1/*
2 * tkCanvText.c --
3 *
4 *	This file implements text items for canvas widgets.
5 *
6 * Copyright (c) 1991-1994 The Regents of the University of California.
7 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
8 *
9 * See the file "license.terms" for information on usage and redistribution
10 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11 *
12 * RCS: @(#) $Id: tkCanvText.c,v 1.15.2.3 2007/04/29 02:24:01 das Exp $
13 */
14
15#include <stdio.h>
16#include "tkInt.h"
17#include "tkCanvas.h"
18#include "tkPort.h"
19#include "default.h"
20
21/*
22 * The structure below defines the record for each text item.
23 */
24
25typedef struct TextItem  {
26    Tk_Item header;		/* Generic stuff that's the same for all
27				 * types.  MUST BE FIRST IN STRUCTURE. */
28    Tk_CanvasTextInfo *textInfoPtr;
29				/* Pointer to a structure containing
30				 * information about the selection and
31				 * insertion cursor.  The structure is owned
32				 * by (and shared with) the generic canvas
33				 * code. */
34    /*
35     * Fields that are set by widget commands other than "configure".
36     */
37
38    double x, y;		/* Positioning point for text. */
39    int insertPos;		/* Character index of character just before
40				 * which the insertion cursor is displayed. */
41
42    /*
43     * Configuration settings that are updated by Tk_ConfigureWidget.
44     */
45
46    Tk_Anchor anchor;		/* Where to anchor text relative to (x,y). */
47    Tk_TSOffset tsoffset;
48    XColor *color;		/* Color for text. */
49    XColor *activeColor;	/* Color for text. */
50    XColor *disabledColor;	/* Color for text. */
51    Tk_Font tkfont;		/* Font for drawing text. */
52    Tk_Justify justify;		/* Justification mode for text. */
53    Pixmap stipple;		/* Stipple bitmap for text, or None. */
54    Pixmap activeStipple;	/* Stipple bitmap for text, or None. */
55    Pixmap disabledStipple;	/* Stipple bitmap for text, or None. */
56    char *text;			/* Text for item (malloc-ed). */
57    int width;			/* Width of lines for word-wrap, pixels.
58				 * Zero means no word-wrap. */
59
60    /*
61     * Fields whose values are derived from the current values of the
62     * configuration settings above.
63     */
64
65    int numChars;		/* Length of text in characters. */
66    int numBytes;		/* Length of text in bytes. */
67    Tk_TextLayout textLayout;	/* Cached text layout information. */
68    int leftEdge;		/* Pixel location of the left edge of the
69				 * text item; where the left border of the
70				 * text layout is drawn. */
71    int rightEdge;		/* Pixel just to right of right edge of
72				 * area of text item.  Used for selecting up
73				 * to end of line. */
74    GC gc;			/* Graphics context for drawing text. */
75    GC selTextGC;		/* Graphics context for selected text. */
76    GC cursorOffGC;		/* If not None, this gives a graphics context
77				 * to use to draw the insertion cursor when
78				 * it's off.  Used if the selection and
79				 * insertion cursor colors are the same.  */
80} TextItem;
81
82/*
83 * Information used for parsing configuration specs:
84 */
85
86static Tk_CustomOption stateOption = {
87    (Tk_OptionParseProc *) TkStateParseProc,
88    TkStatePrintProc, (ClientData) 2
89};
90static Tk_CustomOption tagsOption = {
91    (Tk_OptionParseProc *) Tk_CanvasTagsParseProc,
92    Tk_CanvasTagsPrintProc, (ClientData) NULL
93};
94static Tk_CustomOption offsetOption = {
95    (Tk_OptionParseProc *) TkOffsetParseProc,
96    TkOffsetPrintProc, (ClientData) (TK_OFFSET_RELATIVE)
97};
98
99static Tk_ConfigSpec configSpecs[] = {
100    {TK_CONFIG_COLOR, "-activefill", (char *) NULL, (char *) NULL,
101	(char *) NULL, Tk_Offset(TextItem, activeColor), TK_CONFIG_NULL_OK},
102    {TK_CONFIG_BITMAP, "-activestipple", (char *) NULL, (char *) NULL,
103	(char *) NULL, Tk_Offset(TextItem, activeStipple), TK_CONFIG_NULL_OK},
104    {TK_CONFIG_ANCHOR, "-anchor", (char *) NULL, (char *) NULL,
105	"center", Tk_Offset(TextItem, anchor),
106	TK_CONFIG_DONT_SET_DEFAULT},
107    {TK_CONFIG_COLOR, "-disabledfill", (char *) NULL, (char *) NULL,
108	(char *) NULL, Tk_Offset(TextItem, disabledColor), TK_CONFIG_NULL_OK},
109    {TK_CONFIG_BITMAP, "-disabledstipple", (char *) NULL, (char *) NULL,
110	(char *) NULL, Tk_Offset(TextItem, disabledStipple), TK_CONFIG_NULL_OK},
111    {TK_CONFIG_COLOR, "-fill", (char *) NULL, (char *) NULL,
112	"black", Tk_Offset(TextItem, color), TK_CONFIG_NULL_OK},
113    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
114	DEF_CANVTEXT_FONT, Tk_Offset(TextItem, tkfont), 0},
115    {TK_CONFIG_JUSTIFY, "-justify", (char *) NULL, (char *) NULL,
116	"left", Tk_Offset(TextItem, justify),
117	TK_CONFIG_DONT_SET_DEFAULT},
118    {TK_CONFIG_CUSTOM, "-offset", (char *) NULL, (char *) NULL,
119	"0,0", Tk_Offset(TextItem, tsoffset),
120	TK_CONFIG_DONT_SET_DEFAULT, &offsetOption},
121    {TK_CONFIG_CUSTOM, "-state", (char *) NULL, (char *) NULL,
122	(char *) NULL, Tk_Offset(Tk_Item, state), TK_CONFIG_NULL_OK,
123	&stateOption},
124    {TK_CONFIG_BITMAP, "-stipple", (char *) NULL, (char *) NULL,
125	(char *) NULL, Tk_Offset(TextItem, stipple), TK_CONFIG_NULL_OK},
126    {TK_CONFIG_CUSTOM, "-tags", (char *) NULL, (char *) NULL,
127	(char *) NULL, 0, TK_CONFIG_NULL_OK, &tagsOption},
128    {TK_CONFIG_STRING, "-text", (char *) NULL, (char *) NULL,
129	"", Tk_Offset(TextItem, text), 0},
130    {TK_CONFIG_PIXELS, "-width", (char *) NULL, (char *) NULL,
131	"0", Tk_Offset(TextItem, width), TK_CONFIG_DONT_SET_DEFAULT},
132    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
133	(char *) NULL, 0, 0}
134};
135
136/*
137 * Prototypes for procedures defined in this file:
138 */
139
140static void		ComputeTextBbox _ANSI_ARGS_((Tk_Canvas canvas,
141			    TextItem *textPtr));
142static int		ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
143			    Tk_Canvas canvas, Tk_Item *itemPtr, int argc,
144			    Tcl_Obj *CONST objv[], int flags));
145static int		CreateText _ANSI_ARGS_((Tcl_Interp *interp,
146			    Tk_Canvas canvas, struct Tk_Item *itemPtr,
147			    int argc, Tcl_Obj *CONST objv[]));
148static void		DeleteText _ANSI_ARGS_((Tk_Canvas canvas,
149			    Tk_Item *itemPtr, Display *display));
150static void		DisplayCanvText _ANSI_ARGS_((Tk_Canvas canvas,
151			    Tk_Item *itemPtr, Display *display, Drawable dst,
152			    int x, int y, int width, int height));
153static int		GetSelText _ANSI_ARGS_((Tk_Canvas canvas,
154			    Tk_Item *itemPtr, int offset, char *buffer,
155			    int maxBytes));
156static int		GetTextIndex _ANSI_ARGS_((Tcl_Interp *interp,
157			    Tk_Canvas canvas, Tk_Item *itemPtr,
158			    Tcl_Obj *obj, int *indexPtr));
159static void		ScaleText _ANSI_ARGS_((Tk_Canvas canvas,
160			    Tk_Item *itemPtr, double originX, double originY,
161			    double scaleX, double scaleY));
162static void		SetTextCursor _ANSI_ARGS_((Tk_Canvas canvas,
163			    Tk_Item *itemPtr, int index));
164static int		TextCoords _ANSI_ARGS_((Tcl_Interp *interp,
165			    Tk_Canvas canvas, Tk_Item *itemPtr,
166			    int argc, Tcl_Obj *CONST objv[]));
167static void		TextDeleteChars _ANSI_ARGS_((Tk_Canvas canvas,
168			    Tk_Item *itemPtr, int first, int last));
169static void		TextInsert _ANSI_ARGS_((Tk_Canvas canvas,
170			    Tk_Item *itemPtr, int beforeThis, char *string));
171static int		TextToArea _ANSI_ARGS_((Tk_Canvas canvas,
172			    Tk_Item *itemPtr, double *rectPtr));
173static double		TextToPoint _ANSI_ARGS_((Tk_Canvas canvas,
174			    Tk_Item *itemPtr, double *pointPtr));
175static int		TextToPostscript _ANSI_ARGS_((Tcl_Interp *interp,
176			    Tk_Canvas canvas, Tk_Item *itemPtr, int prepass));
177static void		TranslateText _ANSI_ARGS_((Tk_Canvas canvas,
178			    Tk_Item *itemPtr, double deltaX, double deltaY));
179
180/*
181 * The structures below defines the rectangle and oval item types
182 * by means of procedures that can be invoked by generic item code.
183 */
184
185Tk_ItemType tkTextType = {
186    "text",			/* name */
187    sizeof(TextItem),		/* itemSize */
188    CreateText,			/* createProc */
189    configSpecs,		/* configSpecs */
190    ConfigureText,		/* configureProc */
191    TextCoords,			/* coordProc */
192    DeleteText,			/* deleteProc */
193    DisplayCanvText,		/* displayProc */
194    TK_CONFIG_OBJS,		/* flags */
195    TextToPoint,		/* pointProc */
196    TextToArea,			/* areaProc */
197    TextToPostscript,		/* postscriptProc */
198    ScaleText,			/* scaleProc */
199    TranslateText,		/* translateProc */
200    (Tk_ItemIndexProc *) GetTextIndex,/* indexProc */
201    SetTextCursor,		/* icursorProc */
202    GetSelText,			/* selectionProc */
203    TextInsert,			/* insertProc */
204    TextDeleteChars,		/* dTextProc */
205    (Tk_ItemType *) NULL,	/* nextPtr */
206};
207
208/*
209 *--------------------------------------------------------------
210 *
211 * CreateText --
212 *
213 *	This procedure is invoked to create a new text item
214 *	in a canvas.
215 *
216 * Results:
217 *	A standard Tcl return value.  If an error occurred in
218 *	creating the item then an error message is left in
219 *	the interp's result;  in this case itemPtr is left uninitialized
220 *	so it can be safely freed by the caller.
221 *
222 * Side effects:
223 *	A new text item is created.
224 *
225 *--------------------------------------------------------------
226 */
227
228static int
229CreateText(interp, canvas, itemPtr, objc, objv)
230    Tcl_Interp *interp;		/* Interpreter for error reporting. */
231    Tk_Canvas canvas;		/* Canvas to hold new item. */
232    Tk_Item *itemPtr;		/* Record to hold new item; header has been
233				 * initialized by caller. */
234    int objc;			/* Number of arguments in objv. */
235    Tcl_Obj *CONST objv[];	/* Arguments describing rectangle. */
236{
237    TextItem *textPtr = (TextItem *) itemPtr;
238    int i;
239
240    if (objc == 0) {
241	panic("canvas did not pass any coords\n");
242    }
243
244    /*
245     * Carry out initialization that is needed in order to clean up after
246     * errors during the the remainder of this procedure.
247     */
248
249    textPtr->textInfoPtr = Tk_CanvasGetTextInfo(canvas);
250
251    textPtr->insertPos	= 0;
252
253    textPtr->anchor	= TK_ANCHOR_CENTER;
254    textPtr->tsoffset.flags = 0;
255    textPtr->tsoffset.xoffset = 0;
256    textPtr->tsoffset.yoffset = 0;
257    textPtr->color	= NULL;
258    textPtr->activeColor = NULL;
259    textPtr->disabledColor = NULL;
260    textPtr->tkfont	= NULL;
261    textPtr->justify	= TK_JUSTIFY_LEFT;
262    textPtr->stipple	= None;
263    textPtr->activeStipple = None;
264    textPtr->disabledStipple = None;
265    textPtr->text	= NULL;
266    textPtr->width	= 0;
267
268    textPtr->numChars	= 0;
269    textPtr->numBytes	= 0;
270    textPtr->textLayout = NULL;
271    textPtr->leftEdge	= 0;
272    textPtr->rightEdge	= 0;
273    textPtr->gc		= None;
274    textPtr->selTextGC	= None;
275    textPtr->cursorOffGC = None;
276
277    /*
278     * Process the arguments to fill in the item record.
279     * Only 1 (list) or 2 (x y) coords are allowed.
280     */
281
282    if (objc == 1) {
283	i = 1;
284    } else {
285	char *arg = Tcl_GetString(objv[1]);
286	i = 2;
287	if ((arg[0] == '-') && (arg[1] >= 'a') && (arg[1] <= 'z')) {
288	    i = 1;
289	}
290    }
291    if ((TextCoords(interp, canvas, itemPtr, i, objv) != TCL_OK)) {
292	goto error;
293    }
294    if (ConfigureText(interp, canvas, itemPtr, objc-i, objv+i, 0) == TCL_OK) {
295	return TCL_OK;
296    }
297
298    error:
299    DeleteText(canvas, itemPtr, Tk_Display(Tk_CanvasTkwin(canvas)));
300    return TCL_ERROR;
301}
302
303/*
304 *--------------------------------------------------------------
305 *
306 * TextCoords --
307 *
308 *	This procedure is invoked to process the "coords" widget
309 *	command on text items.  See the user documentation for
310 *	details on what it does.
311 *
312 * Results:
313 *	Returns TCL_OK or TCL_ERROR, and sets the interp's result.
314 *
315 * Side effects:
316 *	The coordinates for the given item may be changed.
317 *
318 *--------------------------------------------------------------
319 */
320
321static int
322TextCoords(interp, canvas, itemPtr, objc, objv)
323    Tcl_Interp *interp;		/* Used for error reporting. */
324    Tk_Canvas canvas;		/* Canvas containing item. */
325    Tk_Item *itemPtr;		/* Item whose coordinates are to be read or
326				 * modified. */
327    int objc;			/* Number of coordinates supplied in objv. */
328    Tcl_Obj *CONST objv[];	/* Array of coordinates: x1, y1, x2, y2, ... */
329{
330    TextItem *textPtr = (TextItem *) itemPtr;
331
332    if (objc == 0) {
333	Tcl_Obj *obj = Tcl_NewObj();
334	Tcl_Obj *subobj = Tcl_NewDoubleObj(textPtr->x);
335	Tcl_ListObjAppendElement(interp, obj, subobj);
336	subobj = Tcl_NewDoubleObj(textPtr->y);
337	Tcl_ListObjAppendElement(interp, obj, subobj);
338	Tcl_SetObjResult(interp, obj);
339    } else if (objc < 3) {
340	if (objc==1) {
341	    if (Tcl_ListObjGetElements(interp, objv[0], &objc,
342		    (Tcl_Obj ***) &objv) != TCL_OK) {
343		return TCL_ERROR;
344	    } else if (objc != 2) {
345		char buf[64 + TCL_INTEGER_SPACE];
346
347		sprintf(buf, "wrong # coordinates: expected 2, got %d", objc);
348		Tcl_SetResult(interp, buf, TCL_VOLATILE);
349		return TCL_ERROR;
350	    }
351	}
352	if ((Tk_CanvasGetCoordFromObj(interp, canvas, objv[0], &textPtr->x) != TCL_OK)
353		|| (Tk_CanvasGetCoordFromObj(interp, canvas, objv[1],
354  		    &textPtr->y) != TCL_OK)) {
355	    return TCL_ERROR;
356	}
357	ComputeTextBbox(canvas, textPtr);
358    } else {
359	char buf[64 + TCL_INTEGER_SPACE];
360
361	sprintf(buf, "wrong # coordinates: expected 0 or 2, got %d", objc);
362	Tcl_SetResult(interp, buf, TCL_VOLATILE);
363	return TCL_ERROR;
364    }
365    return TCL_OK;
366}
367
368/*
369 *--------------------------------------------------------------
370 *
371 * ConfigureText --
372 *
373 *	This procedure is invoked to configure various aspects
374 *	of a text item, such as its border and background colors.
375 *
376 * Results:
377 *	A standard Tcl result code.  If an error occurs, then
378 *	an error message is left in the interp's result.
379 *
380 * Side effects:
381 *	Configuration information, such as colors and stipple
382 *	patterns, may be set for itemPtr.
383 *
384 *--------------------------------------------------------------
385 */
386
387static int
388ConfigureText(interp, canvas, itemPtr, objc, objv, flags)
389    Tcl_Interp *interp;		/* Interpreter for error reporting. */
390    Tk_Canvas canvas;		/* Canvas containing itemPtr. */
391    Tk_Item *itemPtr;		/* Rectangle item to reconfigure. */
392    int objc;			/* Number of elements in objv.  */
393    Tcl_Obj *CONST objv[];	/* Arguments describing things to configure. */
394    int flags;			/* Flags to pass to Tk_ConfigureWidget. */
395{
396    TextItem *textPtr = (TextItem *) itemPtr;
397    XGCValues gcValues;
398    GC newGC, newSelGC;
399    unsigned long mask;
400    Tk_Window tkwin;
401    Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr;
402    XColor *selBgColorPtr;
403    XColor *color;
404    Pixmap stipple;
405    Tk_State state;
406
407    tkwin = Tk_CanvasTkwin(canvas);
408    if (TCL_OK != Tk_ConfigureWidget(interp, tkwin, configSpecs, objc,
409	    (CONST char **) objv, (char *) textPtr, flags|TK_CONFIG_OBJS)) {
410	return TCL_ERROR;
411    }
412
413    /*
414     * A few of the options require additional processing, such as
415     * graphics contexts.
416     */
417
418    state = itemPtr->state;
419
420    if (textPtr->activeColor != NULL ||
421	    textPtr->activeStipple != None) {
422	itemPtr->redraw_flags |= TK_ITEM_STATE_DEPENDANT;
423    } else {
424	itemPtr->redraw_flags &= ~TK_ITEM_STATE_DEPENDANT;
425    }
426
427    if(state == TK_STATE_NULL) {
428	state = ((TkCanvas *)canvas)->canvas_state;
429    }
430
431    color = textPtr->color;
432    stipple = textPtr->stipple;
433    if (((TkCanvas *)canvas)->currentItemPtr == itemPtr) {
434	if (textPtr->activeColor!=NULL) {
435	    color = textPtr->activeColor;
436	}
437	if (textPtr->activeStipple!=None) {
438	    stipple = textPtr->activeStipple;
439	}
440    } else if (state==TK_STATE_DISABLED) {
441	if (textPtr->disabledColor!=NULL) {
442	    color = textPtr->disabledColor;
443	}
444	if (textPtr->disabledStipple!=None) {
445	    stipple = textPtr->disabledStipple;
446	}
447    }
448
449    newGC = newSelGC = None;
450    if (textPtr->tkfont != NULL) {
451	gcValues.font = Tk_FontId(textPtr->tkfont);
452	mask = GCFont;
453	if (color != NULL) {
454	    gcValues.foreground = color->pixel;
455	    mask |= GCForeground;
456	    if (stipple != None) {
457		gcValues.stipple = stipple;
458		gcValues.fill_style = FillStippled;
459		mask |= GCStipple|GCFillStyle;
460	    }
461	    newGC = Tk_GetGC(tkwin, mask, &gcValues);
462	}
463	mask &= ~(GCTile|GCFillStyle|GCStipple);
464	if (stipple != None) {
465	    gcValues.stipple = stipple;
466	    gcValues.fill_style = FillStippled;
467	    mask |= GCStipple|GCFillStyle;
468	}
469	if (textInfoPtr->selFgColorPtr != NULL) {
470	    gcValues.foreground = textInfoPtr->selFgColorPtr->pixel;
471	}
472	newSelGC = Tk_GetGC(tkwin, mask|GCForeground, &gcValues);
473    }
474    if (textPtr->gc != None) {
475	Tk_FreeGC(Tk_Display(tkwin), textPtr->gc);
476    }
477    textPtr->gc = newGC;
478    if (textPtr->selTextGC != None) {
479	Tk_FreeGC(Tk_Display(tkwin), textPtr->selTextGC);
480    }
481    textPtr->selTextGC = newSelGC;
482
483    selBgColorPtr = Tk_3DBorderColor(textInfoPtr->selBorder);
484    if (Tk_3DBorderColor(textInfoPtr->insertBorder)->pixel
485	    == selBgColorPtr->pixel) {
486	if (selBgColorPtr->pixel == BlackPixelOfScreen(Tk_Screen(tkwin))) {
487	    gcValues.foreground = WhitePixelOfScreen(Tk_Screen(tkwin));
488	} else {
489	    gcValues.foreground = BlackPixelOfScreen(Tk_Screen(tkwin));
490	}
491	newGC = Tk_GetGC(tkwin, GCForeground, &gcValues);
492    } else {
493	newGC = None;
494    }
495    if (textPtr->cursorOffGC != None) {
496	Tk_FreeGC(Tk_Display(tkwin), textPtr->cursorOffGC);
497    }
498    textPtr->cursorOffGC = newGC;
499
500
501    /*
502     * If the text was changed, move the selection and insertion indices
503     * to keep them inside the item.
504     */
505
506    textPtr->numBytes = strlen(textPtr->text);
507    textPtr->numChars = Tcl_NumUtfChars(textPtr->text, textPtr->numBytes);
508    if (textInfoPtr->selItemPtr == itemPtr) {
509
510	if (textInfoPtr->selectFirst >= textPtr->numChars) {
511	    textInfoPtr->selItemPtr = NULL;
512	} else {
513	    if (textInfoPtr->selectLast >= textPtr->numChars) {
514		textInfoPtr->selectLast = textPtr->numChars - 1;
515	    }
516	    if ((textInfoPtr->anchorItemPtr == itemPtr)
517		    && (textInfoPtr->selectAnchor >= textPtr->numChars)) {
518		textInfoPtr->selectAnchor = textPtr->numChars - 1;
519	    }
520	}
521    }
522    if (textPtr->insertPos >= textPtr->numChars) {
523	textPtr->insertPos = textPtr->numChars;
524    }
525
526    ComputeTextBbox(canvas, textPtr);
527    return TCL_OK;
528}
529
530/*
531 *--------------------------------------------------------------
532 *
533 * DeleteText --
534 *
535 *	This procedure is called to clean up the data structure
536 *	associated with a text item.
537 *
538 * Results:
539 *	None.
540 *
541 * Side effects:
542 *	Resources associated with itemPtr are released.
543 *
544 *--------------------------------------------------------------
545 */
546
547static void
548DeleteText(canvas, itemPtr, display)
549    Tk_Canvas canvas;		/* Info about overall canvas widget. */
550    Tk_Item *itemPtr;		/* Item that is being deleted. */
551    Display *display;		/* Display containing window for canvas. */
552{
553    TextItem *textPtr = (TextItem *) itemPtr;
554
555    if (textPtr->color != NULL) {
556	Tk_FreeColor(textPtr->color);
557    }
558    if (textPtr->activeColor != NULL) {
559	Tk_FreeColor(textPtr->activeColor);
560    }
561    if (textPtr->disabledColor != NULL) {
562	Tk_FreeColor(textPtr->disabledColor);
563    }
564    Tk_FreeFont(textPtr->tkfont);
565    if (textPtr->stipple != None) {
566	Tk_FreeBitmap(display, textPtr->stipple);
567    }
568    if (textPtr->activeStipple != None) {
569	Tk_FreeBitmap(display, textPtr->activeStipple);
570    }
571    if (textPtr->disabledStipple != None) {
572	Tk_FreeBitmap(display, textPtr->disabledStipple);
573    }
574    if (textPtr->text != NULL) {
575	ckfree(textPtr->text);
576    }
577
578    Tk_FreeTextLayout(textPtr->textLayout);
579    if (textPtr->gc != None) {
580	Tk_FreeGC(display, textPtr->gc);
581    }
582    if (textPtr->selTextGC != None) {
583	Tk_FreeGC(display, textPtr->selTextGC);
584    }
585    if (textPtr->cursorOffGC != None) {
586	Tk_FreeGC(display, textPtr->cursorOffGC);
587    }
588}
589
590/*
591 *--------------------------------------------------------------
592 *
593 * ComputeTextBbox --
594 *
595 *	This procedure is invoked to compute the bounding box of
596 *	all the pixels that may be drawn as part of a text item.
597 *	In addition, it recomputes all of the geometry information
598 *	used to display a text item or check for mouse hits.
599 *
600 * Results:
601 *	None.
602 *
603 * Side effects:
604 *	The fields x1, y1, x2, and y2 are updated in the header
605 *	for itemPtr, and the linePtr structure is regenerated
606 *	for itemPtr.
607 *
608 *--------------------------------------------------------------
609 */
610
611static void
612ComputeTextBbox(canvas, textPtr)
613    Tk_Canvas canvas;		/* Canvas that contains item. */
614    TextItem *textPtr;		/* Item whose bbox is to be recomputed. */
615{
616    Tk_CanvasTextInfo *textInfoPtr;
617    int leftX, topY, width, height, fudge;
618    Tk_State state = textPtr->header.state;
619
620    if(state == TK_STATE_NULL) {
621	state = ((TkCanvas *)canvas)->canvas_state;
622    }
623
624    Tk_FreeTextLayout(textPtr->textLayout);
625    textPtr->textLayout = Tk_ComputeTextLayout(textPtr->tkfont,
626	    textPtr->text, textPtr->numChars, textPtr->width,
627	    textPtr->justify, 0, &width, &height);
628
629    if (state == TK_STATE_HIDDEN || textPtr->color == NULL) {
630	width = height = 0;
631    }
632
633    /*
634     * Use overall geometry information to compute the top-left corner
635     * of the bounding box for the text item.
636     */
637
638    leftX = (int) floor(textPtr->x + 0.5);
639    topY = (int) floor(textPtr->y + 0.5);
640    switch (textPtr->anchor) {
641	case TK_ANCHOR_NW:
642	case TK_ANCHOR_N:
643	case TK_ANCHOR_NE:
644	    break;
645
646	case TK_ANCHOR_W:
647	case TK_ANCHOR_CENTER:
648	case TK_ANCHOR_E:
649	    topY -= height / 2;
650	    break;
651
652	case TK_ANCHOR_SW:
653	case TK_ANCHOR_S:
654	case TK_ANCHOR_SE:
655	    topY -= height;
656	    break;
657    }
658    switch (textPtr->anchor) {
659	case TK_ANCHOR_NW:
660	case TK_ANCHOR_W:
661	case TK_ANCHOR_SW:
662	    break;
663
664	case TK_ANCHOR_N:
665	case TK_ANCHOR_CENTER:
666	case TK_ANCHOR_S:
667	    leftX -= width / 2;
668	    break;
669
670	case TK_ANCHOR_NE:
671	case TK_ANCHOR_E:
672	case TK_ANCHOR_SE:
673	    leftX -= width;
674	    break;
675    }
676
677    textPtr->leftEdge  = leftX;
678    textPtr->rightEdge = leftX + width;
679
680    /*
681     * Last of all, update the bounding box for the item.  The item's
682     * bounding box includes the bounding box of all its lines, plus
683     * an extra fudge factor for the cursor border (which could
684     * potentially be quite large).
685     */
686
687    textInfoPtr = textPtr->textInfoPtr;
688    fudge = (textInfoPtr->insertWidth + 1) / 2;
689    if (textInfoPtr->selBorderWidth > fudge) {
690	fudge = textInfoPtr->selBorderWidth;
691    }
692    textPtr->header.x1 = leftX - fudge;
693    textPtr->header.y1 = topY;
694    textPtr->header.x2 = leftX + width + fudge;
695    textPtr->header.y2 = topY + height;
696}
697
698/*
699 *--------------------------------------------------------------
700 *
701 * DisplayCanvText --
702 *
703 *	This procedure is invoked to draw a text item in a given
704 *	drawable.
705 *
706 * Results:
707 *	None.
708 *
709 * Side effects:
710 *	ItemPtr is drawn in drawable using the transformation
711 *	information in canvas.
712 *
713 *--------------------------------------------------------------
714 */
715
716static void
717DisplayCanvText(canvas, itemPtr, display, drawable, x, y, width, height)
718    Tk_Canvas canvas;		/* Canvas that contains item. */
719    Tk_Item *itemPtr;		/* Item to be displayed. */
720    Display *display;		/* Display on which to draw item. */
721    Drawable drawable;		/* Pixmap or window in which to draw item. */
722    int x, y, width, height;	/* Describes region of canvas that must be
723				 * redisplayed (not used). */
724{
725    TextItem *textPtr;
726    Tk_CanvasTextInfo *textInfoPtr;
727    int selFirstChar, selLastChar;
728    short drawableX, drawableY;
729    Pixmap stipple;
730    Tk_State state = itemPtr->state;
731
732    textPtr = (TextItem *) itemPtr;
733    textInfoPtr = textPtr->textInfoPtr;
734
735    if(state == TK_STATE_NULL) {
736	state = ((TkCanvas *)canvas)->canvas_state;
737    }
738    stipple = textPtr->stipple;
739    if (((TkCanvas *)canvas)->currentItemPtr == itemPtr) {
740	if (textPtr->activeStipple!=None) {
741	    stipple = textPtr->activeStipple;
742	}
743    } else if (state==TK_STATE_DISABLED) {
744	if (textPtr->disabledStipple!=None) {
745	    stipple = textPtr->disabledStipple;
746	}
747    }
748
749    if (textPtr->gc == None) {
750	return;
751    }
752
753    /*
754     * If we're stippling, then modify the stipple offset in the GC.  Be
755     * sure to reset the offset when done, since the GC is supposed to be
756     * read-only.
757     */
758
759    if (stipple != None) {
760	Tk_CanvasSetOffset(canvas, textPtr->gc, &textPtr->tsoffset);
761    }
762
763    selFirstChar = -1;
764    selLastChar = 0;		/* lint. */
765
766    if (textInfoPtr->selItemPtr == itemPtr) {
767	selFirstChar = textInfoPtr->selectFirst;
768	selLastChar = textInfoPtr->selectLast;
769	if (selLastChar > textPtr->numChars) {
770	    selLastChar = textPtr->numChars - 1;
771	}
772	if ((selFirstChar >= 0) && (selFirstChar <= selLastChar)) {
773	    int xFirst, yFirst, hFirst;
774	    int xLast, yLast, wLast;
775
776	    /*
777	     * Draw a special background under the selection.
778	     */
779
780	    Tk_CharBbox(textPtr->textLayout, selFirstChar, &xFirst, &yFirst,
781		    NULL, &hFirst);
782	    Tk_CharBbox(textPtr->textLayout, selLastChar, &xLast, &yLast,
783		    &wLast, NULL);
784
785	    /*
786	     * If the selection spans the end of this line, then display
787	     * selection background all the way to the end of the line.
788	     * However, for the last line we only want to display up to the
789	     * last character, not the end of the line.
790	     */
791
792	    x = xFirst;
793	    height = hFirst;
794	    for (y = yFirst ; y <= yLast; y += height) {
795		if (y == yLast) {
796		    width = xLast + wLast - x;
797		} else {
798		    width = textPtr->rightEdge - textPtr->leftEdge - x;
799		}
800		Tk_CanvasDrawableCoords(canvas,
801			(double) (textPtr->leftEdge + x
802				- textInfoPtr->selBorderWidth),
803			(double) (textPtr->header.y1 + y),
804			&drawableX, &drawableY);
805		Tk_Fill3DRectangle(Tk_CanvasTkwin(canvas), drawable,
806			textInfoPtr->selBorder, drawableX, drawableY,
807			width + 2 * textInfoPtr->selBorderWidth,
808			height, textInfoPtr->selBorderWidth, TK_RELIEF_RAISED);
809		x = 0;
810	    }
811	}
812    }
813
814    /*
815     * If the insertion point should be displayed, then draw a special
816     * background for the cursor before drawing the text.  Note:  if
817     * we're the cursor item but the cursor is turned off, then redraw
818     * background over the area of the cursor.  This guarantees that
819     * the selection won't make the cursor invisible on mono displays,
820     * where both are drawn in the same color.
821     */
822
823    if ((textInfoPtr->focusItemPtr == itemPtr) && (textInfoPtr->gotFocus)) {
824	if (Tk_CharBbox(textPtr->textLayout, textPtr->insertPos,
825		&x, &y, NULL, &height)) {
826	    Tk_CanvasDrawableCoords(canvas,
827		    (double) (textPtr->leftEdge + x
828			    - (textInfoPtr->insertWidth / 2)),
829		    (double) (textPtr->header.y1 + y),
830		    &drawableX, &drawableY);
831	    Tk_SetCaretPos(Tk_CanvasTkwin(canvas), drawableX, drawableY,
832		    height);
833	    if (textInfoPtr->cursorOn) {
834		Tk_Fill3DRectangle(Tk_CanvasTkwin(canvas), drawable,
835			textInfoPtr->insertBorder,
836			drawableX, drawableY,
837			textInfoPtr->insertWidth, height,
838			textInfoPtr->insertBorderWidth, TK_RELIEF_RAISED);
839	    } else if (textPtr->cursorOffGC != None) {
840		/*
841		 * Redraw the background over the area of the cursor,
842		 * even though the cursor is turned off.  This
843		 * guarantees that the selection won't make the cursor
844		 * invisible on mono displays, where both may be drawn
845		 * in the same color.
846		 */
847
848		XFillRectangle(display, drawable, textPtr->cursorOffGC,
849			drawableX, drawableY,
850			(unsigned) textInfoPtr->insertWidth,
851			(unsigned) height);
852	    }
853	}
854    }
855
856
857    /*
858     * If there is no selected text or the selected text foreground
859     * is the same as the regular text foreground, then draw one
860     * text string. If there is selected text and the foregrounds
861     * differ, draw the regular text up to the selection, draw
862     * the selection, then draw the rest of the regular text.
863     * Drawing the regular text and then the selected text over
864     * it would causes problems with anti-aliased text because the
865     * two anti-aliasing colors would blend together.
866     */
867
868    Tk_CanvasDrawableCoords(canvas, (double) textPtr->leftEdge,
869	    (double) textPtr->header.y1, &drawableX, &drawableY);
870
871    if ((selFirstChar >= 0) && (textPtr->selTextGC != textPtr->gc)) {
872	Tk_DrawTextLayout(display, drawable, textPtr->gc, textPtr->textLayout,
873	    drawableX, drawableY, 0, selFirstChar);
874	Tk_DrawTextLayout(display, drawable, textPtr->selTextGC,
875	    textPtr->textLayout, drawableX, drawableY, selFirstChar,
876	    selLastChar + 1);
877	Tk_DrawTextLayout(display, drawable, textPtr->gc, textPtr->textLayout,
878	    drawableX, drawableY, selLastChar + 1, -1);
879    } else {
880	Tk_DrawTextLayout(display, drawable, textPtr->gc, textPtr->textLayout,
881	    drawableX, drawableY, 0, -1);
882    }
883
884    if (stipple != None) {
885	XSetTSOrigin(display, textPtr->gc, 0, 0);
886    }
887}
888
889/*
890 *--------------------------------------------------------------
891 *
892 * TextInsert --
893 *
894 *	Insert characters into a text item at a given position.
895 *
896 * Results:
897 *	None.
898 *
899 * Side effects:
900 *	The text in the given item is modified.  The cursor and
901 *	selection positions are also modified to reflect the
902 *	insertion.
903 *
904 *--------------------------------------------------------------
905 */
906
907static void
908TextInsert(canvas, itemPtr, index, string)
909    Tk_Canvas canvas;		/* Canvas containing text item. */
910    Tk_Item *itemPtr;		/* Text item to be modified. */
911    int index;			/* Character index before which string is
912				 * to be inserted. */
913    char *string;		/* New characters to be inserted. */
914{
915    TextItem *textPtr = (TextItem *) itemPtr;
916    int byteIndex, byteCount, charsAdded;
917    char *new, *text;
918    Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr;
919
920    string = Tcl_GetStringFromObj((Tcl_Obj *) string, &byteCount);
921
922    text = textPtr->text;
923
924    if (index < 0) {
925	index = 0;
926    }
927    if (index > textPtr->numChars) {
928	index = textPtr->numChars;
929    }
930    byteIndex = Tcl_UtfAtIndex(text, index) - text;
931    byteCount = strlen(string);
932    if (byteCount == 0) {
933	return;
934    }
935
936    new = (char *) ckalloc((unsigned) textPtr->numBytes + byteCount + 1);
937    memcpy(new, text, (size_t) byteIndex);
938    strcpy(new + byteIndex, string);
939    strcpy(new + byteIndex + byteCount, text + byteIndex);
940
941    ckfree(text);
942    textPtr->text = new;
943    charsAdded = Tcl_NumUtfChars(string, byteCount);
944    textPtr->numChars += charsAdded;
945    textPtr->numBytes += byteCount;
946
947    /*
948     * Inserting characters invalidates indices such as those for the
949     * selection and cursor.  Update the indices appropriately.
950     */
951
952    if (textInfoPtr->selItemPtr == itemPtr) {
953	if (textInfoPtr->selectFirst >= index) {
954	    textInfoPtr->selectFirst += charsAdded;
955	}
956	if (textInfoPtr->selectLast >= index) {
957	    textInfoPtr->selectLast += charsAdded;
958	}
959	if ((textInfoPtr->anchorItemPtr == itemPtr)
960		&& (textInfoPtr->selectAnchor >= index)) {
961	    textInfoPtr->selectAnchor += charsAdded;
962	}
963    }
964    if (textPtr->insertPos >= index) {
965	textPtr->insertPos += charsAdded;
966    }
967    ComputeTextBbox(canvas, textPtr);
968}
969
970/*
971 *--------------------------------------------------------------
972 *
973 * TextDeleteChars --
974 *
975 *	Delete one or more characters from a text item.
976 *
977 * Results:
978 *	None.
979 *
980 * Side effects:
981 *	Characters between "first" and "last", inclusive, get
982 *	deleted from itemPtr, and things like the selection
983 *	position get updated.
984 *
985 *--------------------------------------------------------------
986 */
987
988static void
989TextDeleteChars(canvas, itemPtr, first, last)
990    Tk_Canvas canvas;		/* Canvas containing itemPtr. */
991    Tk_Item *itemPtr;		/* Item in which to delete characters. */
992    int first;			/* Character index of first character to
993				 * delete. */
994    int last;			/* Character index of last character to
995				 * delete (inclusive). */
996{
997    TextItem *textPtr = (TextItem *) itemPtr;
998    int byteIndex, byteCount, charsRemoved;
999    char *new, *text;
1000    Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr;
1001
1002    text = textPtr->text;
1003    if (first < 0) {
1004	first = 0;
1005    }
1006    if (last >= textPtr->numChars) {
1007	last = textPtr->numChars - 1;
1008    }
1009    if (first > last) {
1010	return;
1011    }
1012    charsRemoved = last + 1 - first;
1013
1014    byteIndex = Tcl_UtfAtIndex(text, first) - text;
1015    byteCount = Tcl_UtfAtIndex(text + byteIndex, charsRemoved)
1016	- (text + byteIndex);
1017
1018    new = (char *) ckalloc((unsigned) (textPtr->numBytes + 1 - byteCount));
1019    memcpy(new, text, (size_t) byteIndex);
1020    strcpy(new + byteIndex, text + byteIndex + byteCount);
1021
1022    ckfree(text);
1023    textPtr->text = new;
1024    textPtr->numChars -= charsRemoved;
1025    textPtr->numBytes -= byteCount;
1026
1027    /*
1028     * Update indexes for the selection and cursor to reflect the
1029     * renumbering of the remaining characters.
1030     */
1031
1032    if (textInfoPtr->selItemPtr == itemPtr) {
1033	if (textInfoPtr->selectFirst > first) {
1034	    textInfoPtr->selectFirst -= charsRemoved;
1035	    if (textInfoPtr->selectFirst < first) {
1036		textInfoPtr->selectFirst = first;
1037	    }
1038	}
1039	if (textInfoPtr->selectLast >= first) {
1040	    textInfoPtr->selectLast -= charsRemoved;
1041	    if (textInfoPtr->selectLast < first - 1) {
1042		textInfoPtr->selectLast = first - 1;
1043	    }
1044	}
1045	if (textInfoPtr->selectFirst > textInfoPtr->selectLast) {
1046	    textInfoPtr->selItemPtr = NULL;
1047	}
1048	if ((textInfoPtr->anchorItemPtr == itemPtr)
1049		&& (textInfoPtr->selectAnchor > first)) {
1050	    textInfoPtr->selectAnchor -= charsRemoved;
1051	    if (textInfoPtr->selectAnchor < first) {
1052		textInfoPtr->selectAnchor = first;
1053	    }
1054	}
1055    }
1056    if (textPtr->insertPos > first) {
1057	textPtr->insertPos -= charsRemoved;
1058	if (textPtr->insertPos < first) {
1059	    textPtr->insertPos = first;
1060	}
1061    }
1062    ComputeTextBbox(canvas, textPtr);
1063    return;
1064}
1065
1066/*
1067 *--------------------------------------------------------------
1068 *
1069 * TextToPoint --
1070 *
1071 *	Computes the distance from a given point to a given
1072 *	text item, in canvas units.
1073 *
1074 * Results:
1075 *	The return value is 0 if the point whose x and y coordinates
1076 *	are pointPtr[0] and pointPtr[1] is inside the text item.  If
1077 *	the point isn't inside the text item then the return value
1078 *	is the distance from the point to the text item.
1079 *
1080 * Side effects:
1081 *	None.
1082 *
1083 *--------------------------------------------------------------
1084 */
1085
1086static double
1087TextToPoint(canvas, itemPtr, pointPtr)
1088    Tk_Canvas canvas;		/* Canvas containing itemPtr. */
1089    Tk_Item *itemPtr;		/* Item to check against point. */
1090    double *pointPtr;		/* Pointer to x and y coordinates. */
1091{
1092    TextItem *textPtr;
1093    Tk_State state = itemPtr->state;
1094    double value;
1095
1096    if (state == TK_STATE_NULL) {
1097	state = ((TkCanvas *)canvas)->canvas_state;
1098    }
1099    textPtr = (TextItem *) itemPtr;
1100    value =  (double) Tk_DistanceToTextLayout(textPtr->textLayout,
1101	    (int) pointPtr[0] - textPtr->leftEdge,
1102	    (int) pointPtr[1] - textPtr->header.y1);
1103
1104    if ((state == TK_STATE_HIDDEN) || (textPtr->color == NULL) ||
1105	    (textPtr->text == NULL) || (*textPtr->text == 0)) {
1106	value = 1.0e36;
1107    }
1108    return value;
1109}
1110
1111/*
1112 *--------------------------------------------------------------
1113 *
1114 * TextToArea --
1115 *
1116 *	This procedure is called to determine whether an item
1117 *	lies entirely inside, entirely outside, or overlapping
1118 *	a given rectangle.
1119 *
1120 * Results:
1121 *	-1 is returned if the item is entirely outside the area
1122 *	given by rectPtr, 0 if it overlaps, and 1 if it is entirely
1123 *	inside the given area.
1124 *
1125 * Side effects:
1126 *	None.
1127 *
1128 *--------------------------------------------------------------
1129 */
1130
1131static int
1132TextToArea(canvas, itemPtr, rectPtr)
1133    Tk_Canvas canvas;		/* Canvas containing itemPtr. */
1134    Tk_Item *itemPtr;		/* Item to check against rectangle. */
1135    double *rectPtr;		/* Pointer to array of four coordinates
1136				 * (x1, y1, x2, y2) describing rectangular
1137				 * area.  */
1138{
1139    TextItem *textPtr;
1140    Tk_State state = itemPtr->state;
1141
1142    if (state == TK_STATE_NULL) {
1143	state = ((TkCanvas *)canvas)->canvas_state;
1144    }
1145
1146    textPtr = (TextItem *) itemPtr;
1147    return Tk_IntersectTextLayout(textPtr->textLayout,
1148	    (int) (rectPtr[0] + 0.5) - textPtr->leftEdge,
1149	    (int) (rectPtr[1] + 0.5) - textPtr->header.y1,
1150	    (int) (rectPtr[2] - rectPtr[0] + 0.5),
1151	    (int) (rectPtr[3] - rectPtr[1] + 0.5));
1152}
1153
1154/*
1155 *--------------------------------------------------------------
1156 *
1157 * ScaleText --
1158 *
1159 *	This procedure is invoked to rescale a text item.
1160 *
1161 * Results:
1162 *	None.
1163 *
1164 * Side effects:
1165 *	Scales the position of the text, but not the size
1166 *	of the font for the text.
1167 *
1168 *--------------------------------------------------------------
1169 */
1170
1171	/* ARGSUSED */
1172static void
1173ScaleText(canvas, itemPtr, originX, originY, scaleX, scaleY)
1174    Tk_Canvas canvas;		/* Canvas containing rectangle. */
1175    Tk_Item *itemPtr;		/* Rectangle to be scaled. */
1176    double originX, originY;	/* Origin about which to scale rect. */
1177    double scaleX;		/* Amount to scale in X direction. */
1178    double scaleY;		/* Amount to scale in Y direction. */
1179{
1180    TextItem *textPtr = (TextItem *) itemPtr;
1181
1182    textPtr->x = originX + scaleX*(textPtr->x - originX);
1183    textPtr->y = originY + scaleY*(textPtr->y - originY);
1184    ComputeTextBbox(canvas, textPtr);
1185    return;
1186}
1187
1188/*
1189 *--------------------------------------------------------------
1190 *
1191 * TranslateText --
1192 *
1193 *	This procedure is called to move a text item by a
1194 *	given amount.
1195 *
1196 * Results:
1197 *	None.
1198 *
1199 * Side effects:
1200 *	The position of the text item is offset by (xDelta, yDelta),
1201 *	and the bounding box is updated in the generic part of the
1202 *	item structure.
1203 *
1204 *--------------------------------------------------------------
1205 */
1206
1207static void
1208TranslateText(canvas, itemPtr, deltaX, deltaY)
1209    Tk_Canvas canvas;		/* Canvas containing item. */
1210    Tk_Item *itemPtr;		/* Item that is being moved. */
1211    double deltaX, deltaY;	/* Amount by which item is to be moved. */
1212{
1213    TextItem *textPtr = (TextItem *) itemPtr;
1214
1215    textPtr->x += deltaX;
1216    textPtr->y += deltaY;
1217    ComputeTextBbox(canvas, textPtr);
1218}
1219
1220/*
1221 *--------------------------------------------------------------
1222 *
1223 * GetTextIndex --
1224 *
1225 *	Parse an index into a text item and return either its value
1226 *	or an error.
1227 *
1228 * Results:
1229 *	A standard Tcl result.  If all went well, then *indexPtr is
1230 *	filled in with the index (into itemPtr) corresponding to
1231 *	string.  Otherwise an error message is left in
1232 *	the interp's result.
1233 *
1234 * Side effects:
1235 *	None.
1236 *
1237 *--------------------------------------------------------------
1238 */
1239
1240static int
1241GetTextIndex(interp, canvas, itemPtr, obj, indexPtr)
1242    Tcl_Interp *interp;		/* Used for error reporting. */
1243    Tk_Canvas canvas;		/* Canvas containing item. */
1244    Tk_Item *itemPtr;		/* Item for which the index is being
1245				 * specified. */
1246    Tcl_Obj *obj;		/* Specification of a particular character
1247				 * in itemPtr's text. */
1248    int *indexPtr;		/* Where to store converted character
1249				 * index. */
1250{
1251    TextItem *textPtr = (TextItem *) itemPtr;
1252    int c, length;
1253    TkCanvas *canvasPtr = (TkCanvas *) canvas;
1254    Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr;
1255    char *string = Tcl_GetStringFromObj(obj, &length);
1256
1257    c = string[0];
1258
1259    if ((c == 'e') && (strncmp(string, "end", (unsigned) length) == 0)) {
1260	*indexPtr = textPtr->numChars;
1261    } else if ((c=='i') && (strncmp(string, "insert", (unsigned) length)==0)) {
1262	*indexPtr = textPtr->insertPos;
1263    } else if ((c=='s') && (strncmp(string, "sel.first", (unsigned) length)==0)
1264	    && (length >= 5)) {
1265	if (textInfoPtr->selItemPtr != itemPtr) {
1266	    Tcl_SetResult(interp, "selection isn't in item", TCL_STATIC);
1267	    return TCL_ERROR;
1268	}
1269	*indexPtr = textInfoPtr->selectFirst;
1270    } else if ((c=='s') && (strncmp(string, "sel.last", (unsigned) length)==0)
1271	    && (length >= 5)) {
1272	if (textInfoPtr->selItemPtr != itemPtr) {
1273	    Tcl_SetResult(interp, "selection isn't in item", TCL_STATIC);
1274	    return TCL_ERROR;
1275	}
1276	*indexPtr = textInfoPtr->selectLast;
1277    } else if (c == '@') {
1278	int x, y;
1279	double tmp;
1280	char *end, *p;
1281
1282	p = string+1;
1283	tmp = strtod(p, &end);
1284	if ((end == p) || (*end != ',')) {
1285	    goto badIndex;
1286	}
1287	x = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);
1288	p = end+1;
1289	tmp = strtod(p, &end);
1290	if ((end == p) || (*end != 0)) {
1291	    goto badIndex;
1292	}
1293	y = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);
1294	*indexPtr = Tk_PointToChar(textPtr->textLayout,
1295		x + canvasPtr->scrollX1 - textPtr->leftEdge,
1296		y + canvasPtr->scrollY1 - textPtr->header.y1);
1297    } else if (Tcl_GetIntFromObj((Tcl_Interp *)NULL, obj, indexPtr) == TCL_OK) {
1298	if (*indexPtr < 0){
1299	    *indexPtr = 0;
1300	} else if (*indexPtr > textPtr->numChars) {
1301	    *indexPtr = textPtr->numChars;
1302	}
1303    } else {
1304	/*
1305	 * Some of the paths here leave messages in the interp's result,
1306	 * so we have to clear it out before storing our own message.
1307	 */
1308
1309	badIndex:
1310	Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
1311	Tcl_AppendResult(interp, "bad index \"", string, "\"",
1312		(char *) NULL);
1313	return TCL_ERROR;
1314    }
1315    return TCL_OK;
1316}
1317
1318/*
1319 *--------------------------------------------------------------
1320 *
1321 * SetTextCursor --
1322 *
1323 *	Set the position of the insertion cursor in this item.
1324 *
1325 * Results:
1326 *	None.
1327 *
1328 * Side effects:
1329 *	The cursor position will change.
1330 *
1331 *--------------------------------------------------------------
1332 */
1333
1334	/* ARGSUSED */
1335static void
1336SetTextCursor(canvas, itemPtr, index)
1337    Tk_Canvas canvas;		/* Record describing canvas widget. */
1338    Tk_Item *itemPtr;		/* Text item in which cursor position is to
1339				 * be set. */
1340    int index;			/* Character index of character just before
1341				 * which cursor is to be positioned. */
1342{
1343    TextItem *textPtr = (TextItem *) itemPtr;
1344
1345    if (index < 0) {
1346	textPtr->insertPos = 0;
1347    } else  if (index > textPtr->numChars) {
1348	textPtr->insertPos = textPtr->numChars;
1349    } else {
1350	textPtr->insertPos = index;
1351    }
1352}
1353
1354/*
1355 *--------------------------------------------------------------
1356 *
1357 * GetSelText --
1358 *
1359 *	This procedure is invoked to return the selected portion
1360 *	of a text item.  It is only called when this item has
1361 *	the selection.
1362 *
1363 * Results:
1364 *	The return value is the number of non-NULL bytes stored
1365 *	at buffer.  Buffer is filled (or partially filled) with a
1366 *	NULL-terminated string containing part or all of the selection,
1367 *	as given by offset and maxBytes.
1368 *
1369 * Side effects:
1370 *	None.
1371 *
1372 *--------------------------------------------------------------
1373 */
1374
1375static int
1376GetSelText(canvas, itemPtr, offset, buffer, maxBytes)
1377    Tk_Canvas canvas;		/* Canvas containing selection. */
1378    Tk_Item *itemPtr;		/* Text item containing selection. */
1379    int offset;			/* Byte offset within selection of first
1380				 * character to be returned. */
1381    char *buffer;		/* Location in which to place selection. */
1382    int maxBytes;		/* Maximum number of bytes to place at
1383				 * buffer, not including terminating NULL
1384				 * character. */
1385{
1386    TextItem *textPtr = (TextItem *) itemPtr;
1387    int byteCount;
1388    char *text;
1389    CONST char *selStart, *selEnd;
1390    Tk_CanvasTextInfo *textInfoPtr = textPtr->textInfoPtr;
1391
1392    if ((textInfoPtr->selectFirst < 0) ||
1393	    (textInfoPtr->selectFirst > textInfoPtr->selectLast)) {
1394	return 0;
1395    }
1396    text = textPtr->text;
1397    selStart = Tcl_UtfAtIndex(text, textInfoPtr->selectFirst);
1398    selEnd = Tcl_UtfAtIndex(selStart,
1399	    textInfoPtr->selectLast + 1 - textInfoPtr->selectFirst);
1400    byteCount = selEnd - selStart - offset;
1401    if (byteCount > maxBytes) {
1402	byteCount = maxBytes;
1403    }
1404    if (byteCount <= 0) {
1405	return 0;
1406    }
1407    memcpy(buffer, selStart + offset, (size_t) byteCount);
1408    buffer[byteCount] = '\0';
1409    return byteCount;
1410}
1411
1412/*
1413 *--------------------------------------------------------------
1414 *
1415 * TextToPostscript --
1416 *
1417 *	This procedure is called to generate Postscript for
1418 *	text items.
1419 *
1420 * Results:
1421 *	The return value is a standard Tcl result.  If an error
1422 *	occurs in generating Postscript then an error message is
1423 *	left in the interp's result, replacing whatever used
1424 *	to be there.  If no error occurs, then Postscript for the
1425 *	item is appended to the result.
1426 *
1427 * Side effects:
1428 *	None.
1429 *
1430 *--------------------------------------------------------------
1431 */
1432
1433static int
1434TextToPostscript(interp, canvas, itemPtr, prepass)
1435    Tcl_Interp *interp;		/* Leave Postscript or error message here. */
1436    Tk_Canvas canvas;		/* Information about overall canvas. */
1437    Tk_Item *itemPtr;		/* Item for which Postscript is wanted. */
1438    int prepass;		/* 1 means this is a prepass to collect
1439				 * font information; 0 means final Postscript
1440				 * is being created. */
1441{
1442    TextItem *textPtr = (TextItem *) itemPtr;
1443    int x, y;
1444    Tk_FontMetrics fm;
1445    char *justify;
1446    char buffer[500];
1447    XColor *color;
1448    Pixmap stipple;
1449    Tk_State state = itemPtr->state;
1450
1451    if(state == TK_STATE_NULL) {
1452	state = ((TkCanvas *)canvas)->canvas_state;
1453    }
1454    color = textPtr->color;
1455    stipple = textPtr->stipple;
1456    if (state == TK_STATE_HIDDEN || textPtr->color == NULL ||
1457	    textPtr->text == NULL || *textPtr->text == 0) {
1458	return TCL_OK;
1459    } else if (((TkCanvas *)canvas)->currentItemPtr == itemPtr) {
1460	if (textPtr->activeColor!=NULL) {
1461	    color = textPtr->activeColor;
1462	}
1463	if (textPtr->activeStipple!=None) {
1464	    stipple = textPtr->activeStipple;
1465	}
1466    } else if (state==TK_STATE_DISABLED) {
1467	if (textPtr->disabledColor!=NULL) {
1468	    color = textPtr->disabledColor;
1469	}
1470	if (textPtr->disabledStipple!=None) {
1471	    stipple = textPtr->disabledStipple;
1472	}
1473    }
1474
1475    if (Tk_CanvasPsFont(interp, canvas, textPtr->tkfont) != TCL_OK) {
1476	return TCL_ERROR;
1477    }
1478    if (prepass != 0) {
1479	return TCL_OK;
1480    }
1481    if (Tk_CanvasPsColor(interp, canvas, color) != TCL_OK) {
1482	return TCL_ERROR;
1483    }
1484    if (stipple != None) {
1485	Tcl_AppendResult(interp, "/StippleText {\n    ",
1486		(char *) NULL);
1487	Tk_CanvasPsStipple(interp, canvas, stipple);
1488	Tcl_AppendResult(interp, "} bind def\n", (char *) NULL);
1489    }
1490
1491    sprintf(buffer, "%.15g %.15g [\n", textPtr->x,
1492	    Tk_CanvasPsY(canvas, textPtr->y));
1493    Tcl_AppendResult(interp, buffer, (char *) NULL);
1494
1495    Tk_TextLayoutToPostscript(interp, textPtr->textLayout);
1496
1497    x = 0;  y = 0;  justify = NULL;	/* lint. */
1498    switch (textPtr->anchor) {
1499	case TK_ANCHOR_NW:	x = 0; y = 0;	break;
1500	case TK_ANCHOR_N:	x = 1; y = 0;	break;
1501	case TK_ANCHOR_NE:	x = 2; y = 0;	break;
1502	case TK_ANCHOR_E:	x = 2; y = 1;	break;
1503	case TK_ANCHOR_SE:	x = 2; y = 2;	break;
1504	case TK_ANCHOR_S:	x = 1; y = 2;	break;
1505	case TK_ANCHOR_SW:	x = 0; y = 2;	break;
1506	case TK_ANCHOR_W:	x = 0; y = 1;	break;
1507	case TK_ANCHOR_CENTER:	x = 1; y = 1;	break;
1508    }
1509    switch (textPtr->justify) {
1510        case TK_JUSTIFY_LEFT:	justify = "0";	break;
1511	case TK_JUSTIFY_CENTER: justify = "0.5";break;
1512	case TK_JUSTIFY_RIGHT:  justify = "1";	break;
1513    }
1514
1515    Tk_GetFontMetrics(textPtr->tkfont, &fm);
1516    sprintf(buffer, "] %d %g %g %s %s DrawText\n",
1517	    fm.linespace, x / -2.0, y / 2.0, justify,
1518	    ((stipple == None) ? "false" : "true"));
1519    Tcl_AppendResult(interp, buffer, (char *) NULL);
1520
1521    return TCL_OK;
1522}
1523