/* * Text.c -- Implementation of Text item. * * Authors : Patrick LECOANET * Creation date : Sat Mar 25 13:58:39 1995 */ /* * Copyright (c) 1993 - 2005 CENA, Patrick Lecoanet -- * * See the file "Copyright" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * */ /* * Some functions in this file are derived from tkCanvText.c and thus * copyrighted: * * Copyright (c) 1991-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * */ #include #include #include #include #include #include "Item.h" #include "Geo.h" #include "Draw.h" #include "Types.h" #include "WidgetInfo.h" #include "tkZinc.h" #include "Image.h" static const char rcsid[] = "$Imagine: Text.c,v 1.13 1997/05/15 11:35:46 lecoanet Exp $"; static const char compile_id[] = "$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $"; /* * Bit offset of flags. */ #define UNDERLINED 1 #define OVERSTRIKED 2 /* ********************************************************************************** * * Specific Text item record * ********************************************************************************** */ typedef struct _TextLineInfo { char *start; /* Index of first char in line */ unsigned short num_bytes; /* Number of displayed bytes in line (NOT chars)*/ unsigned short width; /* Line width in pixels */ unsigned short origin_x; /* X pos for drawing the line */ unsigned short origin_y; } TextLineInfoStruct, *TextLineInfo; typedef struct _TextItemStruct { ZnItemStruct header; /* Public data */ ZnPoint pos; /* Position at anchor */ ZnGradient *color; char *text; ZnImage fill_pattern; Tk_Font font; unsigned short width; short spacing; unsigned short flags; Tk_Anchor anchor; Tk_Anchor connection_anchor; Tk_Justify alignment; /* Private data */ unsigned short num_chars; unsigned short insert_index; ZnList text_info; unsigned short max_width; unsigned short height; ZnPoint poly[4]; #ifdef GL ZnTexFontInfo *tfi; #endif } TextItemStruct, *TextItem; static ZnAttrConfig text_attrs[] = { { ZN_CONFIG_ALIGNMENT, "-alignment", NULL, Tk_Offset(TextItemStruct, alignment), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_ANCHOR, "-anchor", NULL, Tk_Offset(TextItemStruct, anchor), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_GRADIENT, "-color", NULL, Tk_Offset(TextItemStruct, color), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-composealpha", NULL, Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_ALPHA_BIT, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-composerotation", NULL, Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_ROTATION_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composescale", NULL, Tk_Offset(TextItemStruct, header.flags), ZN_COMPOSE_SCALE_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_ITEM, "-connecteditem", NULL, Tk_Offset(TextItemStruct, header.connected_item), 0, ZN_COORDS_FLAG|ZN_ITEM_FLAG, False }, { ZN_CONFIG_ANCHOR, "-connectionanchor", NULL, Tk_Offset(TextItemStruct, connection_anchor), 0, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BITMAP, "-fillpattern", NULL, Tk_Offset(TextItemStruct, fill_pattern), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_FONT, "-font", NULL, Tk_Offset(TextItemStruct, font), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_BOOL, "-overstriked", NULL, Tk_Offset(TextItemStruct, flags), OVERSTRIKED, ZN_DRAW_FLAG, False }, { ZN_CONFIG_POINT, "-position", NULL, Tk_Offset(TextItemStruct, pos), 0, ZN_COORDS_FLAG, False}, { ZN_CONFIG_PRI, "-priority", NULL, Tk_Offset(TextItemStruct, header.priority), 0, ZN_DRAW_FLAG|ZN_REPICK_FLAG, False }, { ZN_CONFIG_BOOL, "-sensitive", NULL, Tk_Offset(TextItemStruct, header.flags), ZN_SENSITIVE_BIT, ZN_REPICK_FLAG, False }, { ZN_CONFIG_SHORT, "-spacing", NULL, Tk_Offset(TextItemStruct, spacing), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_TAG_LIST, "-tags", NULL, Tk_Offset(TextItemStruct, header.tags), 0, 0, False }, { ZN_CONFIG_STRING, "-text", NULL, Tk_Offset(TextItemStruct, text), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_BOOL, "-underlined", NULL, Tk_Offset(TextItemStruct, flags), UNDERLINED, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-visible", NULL, Tk_Offset(TextItemStruct, header.flags), ZN_VISIBLE_BIT, ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False }, { ZN_CONFIG_USHORT, "-width", NULL, Tk_Offset(TextItemStruct, width), 0, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG, False }, { ZN_CONFIG_END, NULL, NULL, 0, 0, 0, False } }; /* ********************************************************************************** * * Init -- * ********************************************************************************** */ static int Init(ZnItem item, int *argc, Tcl_Obj *CONST *args[]) { ZnWInfo *wi = item->wi; TextItem text = (TextItem) item; /*printf("size of a text(header) = %d(%d) %d\n", sizeof(TextItemStruct), sizeof(ZnItemStruct), sizeof(TextLineInfoStruct));*/ text->text_info = NULL; /* Init attributes */ SET(item->flags, ZN_VISIBLE_BIT); SET(item->flags, ZN_SENSITIVE_BIT); SET(item->flags, ZN_COMPOSE_ALPHA_BIT); CLEAR(item->flags, ZN_COMPOSE_ROTATION_BIT); CLEAR(item->flags, ZN_COMPOSE_SCALE_BIT); item->priority = 1; text->pos.x = text->pos.y = 0.0; text->text = NULL; text->num_chars = 0; text->fill_pattern = ZnUnspecifiedImage; text->anchor = TK_ANCHOR_NW; text->connection_anchor = TK_ANCHOR_SW; text->color = ZnGetGradientByValue(wi->fore_color); text->alignment = TK_JUSTIFY_LEFT; text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(wi->font)); #ifdef GL text->tfi = ZnGetTexFont(wi, text->font); #endif text->width = 0; text->spacing = 0; text->insert_index = 0; CLEAR(text->flags, UNDERLINED); CLEAR(text->flags, OVERSTRIKED); return TCL_OK; } /* ********************************************************************************** * * Clone -- * ********************************************************************************** */ static void Clone(ZnItem item) { TextItem text = (TextItem) item; ZnWInfo *wi = item->wi; char *str; if (text->text) { str = ZnMalloc((strlen(text->text) + 1) * sizeof(char)); strcpy(str, text->text); text->text = str; } if (text->fill_pattern != ZnUnspecifiedImage) { text->fill_pattern = ZnGetImageByValue(text->fill_pattern, NULL, NULL); } text->color = ZnGetGradientByValue(text->color); text->font = Tk_GetFont(wi->interp, wi->win, Tk_NameOfFont(text->font)); #ifdef GL text->tfi = ZnGetTexFont(wi, text->font); #endif /* * We always need to invalidate, either because the model * has not done its layout (text_info == NULL) or because * we must unshare the pointers to the text that are in * text_info. */ text->text_info = NULL; ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG); } /* ********************************************************************************** * * Destroy -- * ********************************************************************************** */ static void Destroy(ZnItem item) { TextItem text = (TextItem) item; if (text->text) { ZnFree(text->text); } if (text->fill_pattern != ZnUnspecifiedImage) { ZnFreeImage(text->fill_pattern, NULL, NULL); text->fill_pattern = ZnUnspecifiedImage; } ZnFreeGradient(text->color); Tk_FreeFont(text->font); #ifdef GL if (text->tfi) { ZnFreeTexFont(text->tfi); } #endif if (text->text_info) { ZnListFree(text->text_info); } } /* ********************************************************************************** * * Configure -- * ********************************************************************************** */ static int Configure(ZnItem item, int argc, Tcl_Obj *CONST argv[], int *flags) { TextItem text = (TextItem) item; ZnItem old_connected = item->connected_item; unsigned int num_chars; #ifdef GL Tk_Font old_font = text->font; #endif if (ZnConfigureAttributes(item->wi, item, item, text_attrs, argc, argv, flags) == TCL_ERROR) { return TCL_ERROR; } #ifdef GL if (old_font != text->font) { if (text->tfi) { ZnFreeTexFont(text->tfi); text->tfi = ZnGetTexFont(item->wi, text->font); } } #endif num_chars = 0; if (text->text) { num_chars = Tcl_NumUtfChars(text->text, (int) strlen(text->text)); } if (text->num_chars != num_chars) { ZnTextInfo *ti = &item->wi->text_info; /* * The text has changed, update the selection and * insertion pos to keep them valid. */ if (item == ti->sel_item) { if (ti->sel_last > (int) num_chars) { ti->sel_last = num_chars; } if (ti->sel_first >= ti->sel_last) { ti->sel_item = ZN_NO_ITEM; ti->sel_field = ZN_NO_PART; } if ((ti->anchor_item == item) && (ti->sel_anchor > (int) num_chars)) { ti->sel_anchor = num_chars; } } if (text->insert_index > num_chars) { text->insert_index = num_chars; } text->num_chars = num_chars; } if (ISSET(*flags, ZN_ITEM_FLAG)) { /* * If the new connected item is not appropriate back up * to the old one. */ if ((item->connected_item == ZN_NO_ITEM) || (ISSET(item->connected_item->class->flags, ZN_CLASS_HAS_ANCHORS) && (item->parent == item->connected_item->parent))) { ZnITEM.UpdateItemDependency(item, old_connected); } else { item->connected_item = old_connected; } } return TCL_OK; } /* ********************************************************************************** * * Query -- * ********************************************************************************** */ static int Query(ZnItem item, int argc, Tcl_Obj *CONST argv[]) { if (ZnQueryAttribute(item->wi->interp, item, text_attrs, argv[0]) == TCL_ERROR) { return TCL_ERROR; } return TCL_OK; } /* * Compute the transformation to be used and the origin * of the text (upper left point in item coordinates). */ static ZnTransfo * ComputeTransfoAndOrigin(ZnItem item, ZnPoint *origin) { TextItem text = (TextItem) item; /* * The connected item support anchors, this is checked by configure. */ if (item->connected_item != ZN_NO_ITEM) { ZnTransfo inv; item->connected_item->class->GetAnchor(item->connected_item, text->connection_anchor, origin); /* GetAnchor return a position in device coordinates not in * the item coordinate space. To compute the text origin * (upper left corner), we must apply the inverse transform * to the ref point before calling anchor2origin. */ ZnTransfoInvert(item->transfo, &inv); ZnTransformPoint(&inv, origin, origin); ZnAnchor2Origin(origin, (ZnReal) text->max_width, (ZnReal) text->height, text->anchor, origin); origin->x = ZnNearestInt(origin->x); origin->y = ZnNearestInt(origin->y); /* * The relevant transform in case of an attachment is the item * transform alone. This is case of local coordinate space where * only the translation is a function of the whole transform * stack, scale and rotation are reset. */ return item->transfo; } else { ZnPoint p; p.x = p.y = 0; ZnAnchor2Origin(&p, (ZnReal) text->max_width, (ZnReal) text->height, text->anchor, origin); origin->x = ZnNearestInt(origin->x); origin->y = ZnNearestInt(origin->y); return item->wi->current_transfo; } } /* * Compute the selection and the cursor geometry. */ void ComputeCursor(ZnItem item, int *cursor_line, unsigned int *cursor_offset) { TextItem text = (TextItem) item; ZnWInfo *wi = item->wi; ZnTextInfo *ti = &wi->text_info; TextLineInfo lines, lines_ptr; unsigned int i, line_index, insert_index, num_lines; num_lines = ZnListSize(text->text_info); if (num_lines == 0) { *cursor_line = 0; } lines = ZnListArray(text->text_info); if ((wi->focus_item == item) && ISSET(wi->flags, ZN_GOT_FOCUS) && ti->cursor_on) { insert_index = Tcl_UtfAtIndex(text->text, (int) text->insert_index)-text->text; for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { /* * Mark the line with the cursor and compute its * position along the X axis. */ line_index = lines_ptr->start - text->text; if ((insert_index >= line_index) && (insert_index <= line_index + lines_ptr->num_bytes)) { *cursor_line = i; *cursor_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start, insert_index - line_index); } } } } static void ComputeSelection(ZnItem item, int *sel_first_line, int *sel_last_line, unsigned int *sel_start_offset, unsigned int *sel_stop_offset) { TextItem text = (TextItem) item; ZnWInfo *wi = item->wi; ZnTextInfo *ti = &wi->text_info; TextLineInfo lines_ptr, lines; int i, num_lines, byte_index; unsigned int line_index; unsigned int sel_first, sel_last; num_lines = ZnListSize(text->text_info); if ((ti->sel_item != item) || !num_lines) { return; } lines = ZnListArray(text->text_info); sel_first = Tcl_UtfAtIndex(text->text, ti->sel_first)-text->text; sel_last = Tcl_UtfAtIndex(text->text, ti->sel_last+1)-text->text; for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { /* * Compute the selection first and last line as well as * the positions along the X axis. */ line_index = lines_ptr->start - text->text; if ((sel_last >= line_index) && (sel_first <= (line_index + lines_ptr->num_bytes))) { if (*sel_first_line < 0) { byte_index = sel_first - line_index; if (byte_index <= 0) { *sel_first_line = i; *sel_start_offset = 0; /*printf("sel_start_offset 1 : %d\n", *sel_start_offset);*/ } else if (byte_index <= lines_ptr->num_bytes) { *sel_first_line = i; *sel_start_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start, byte_index); /*printf("sel_start_offset 2 : %d\n", *sel_start_offset);*/ } } byte_index = ti->sel_last+1 - line_index; *sel_last_line = i; if (byte_index == lines_ptr->num_bytes+1) *sel_stop_offset = lines_ptr->width; else if (byte_index <= lines_ptr->num_bytes) *sel_stop_offset = Tk_TextWidth(text->font, (char *) lines_ptr->start, byte_index); } } } /* ********************************************************************************** * * ComputeCoordinates -- * ********************************************************************************** */ static void ComputeCoordinates(ZnItem item, ZnBool force) { ZnWInfo *wi = item->wi; TextItem text = (TextItem) item; TextLineInfo infos; Tk_FontMetrics fm; ZnTransfo *transfo; int i, num_lines, cur_dy, font_height; ZnResetBBox(&item->item_bounding_box); Tk_GetFontMetrics(text->font, &fm); font_height = fm.ascent+fm.descent; /* * The layout need not be done each time the item is moved, scaled * or rotated. */ if (ISSET(item->inv_flags, ZN_LAYOUT_FLAG)) { char *scan; int wrap, prev_num_lines; text->max_width = 0; if (text->text_info != NULL) { prev_num_lines = ZnListSize(text->text_info); ZnListEmpty(text->text_info); } else { prev_num_lines = 0; text->text_info = ZnListNew(1, sizeof(TextLineInfoStruct)); } if (text->width > 0) { wrap = text->width; } else { wrap = 100000; } if ((scan = text->text) != NULL) { TextLineInfoStruct info; while (*scan) { char *special; int num, w; /* * Limit the excursion of Tk_MeasureChars to the end * of the line. Do not include \n in the measure done. */ num = strcspn(scan, "\r\n"); special = scan + num; info.num_bytes = Tk_MeasureChars(text->font, scan, num, wrap, TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &w); info.width = w; info.start = scan; text->max_width = MAX(info.width, text->max_width); scan += info.num_bytes; /* * Skip the newline line character. */ if ((*scan == '\r') || (*scan == '\n')) { scan++; } else { /* * Skip white spaces occuring after an * automatic line break. */ while (*scan == ' ') { scan++; } } ZnListAdd(text->text_info, &info, ZnListTail); /*printf("adding a text info : %s, num_bytes : %d, width : %d\n", info.start, info.num_bytes, info.width);*/ } if (*text->text && ((scan[-1] == '\r') || (scan[-1] == '\n'))) { /* Build a text info even for an empty line * at the end of text or for an empty text. * It is needed to enable selection and cursor * insertion to behave correctly. */ info.num_bytes = 0; info.width = 0; info.start = scan; ZnListAdd(text->text_info, &info, ZnListTail); } } /* * Compute x & y positions for all lines in text_info. The coordinates are * in text natural units NOT transformed units. */ cur_dy = fm.ascent; num_lines = ZnListSize(text->text_info); infos = (TextLineInfo) ZnListArray(text->text_info); for (i = 0; i < num_lines; i++) { switch (text->alignment) { case TK_JUSTIFY_LEFT: infos[i].origin_x = 0; break; case TK_JUSTIFY_CENTER: infos[i].origin_x = (text->max_width + 1 - infos[i].width)/2; break; case TK_JUSTIFY_RIGHT: infos[i].origin_x = text->max_width + 1 - infos[i].width; break; } infos[i].origin_y = cur_dy; cur_dy += font_height + text->spacing; /*printf("fixing line %d x : %d, y : %d\n", i, infos[i].origin_x, infos[i].origin_y);*/ } } /* ISSET(item->inv_flags, INV_TEXT_LAYOUT) */ text->height = font_height; if (text->text_info && text->max_width) { unsigned int h, cursor_offset; int cursor_line; ZnPoint origin, box[4]; num_lines = ZnListSize(text->text_info); infos = ZnListArray(text->text_info); h = num_lines * font_height + (num_lines-1) * text->spacing; text->height = MAX(text->height, h); transfo = ComputeTransfoAndOrigin(item, &origin); text->poly[0].x = origin.x; text->poly[0].y = origin.y; text->poly[3].x = text->poly[0].x + text->max_width; text->poly[3].y = text->poly[0].y + text->height; text->poly[1].x = text->poly[0].x; text->poly[1].y = text->poly[3].y; text->poly[2].x = text->poly[3].x; text->poly[2].y = text->poly[0].y; ZnTransformPoints(transfo, text->poly, text->poly, 4); /* * Add to the bounding box. */ ZnAddPointsToBBox(&item->item_bounding_box, text->poly, 4); /* * Add the cursor shape to the bbox. */ cursor_line = -1; ComputeCursor(item, &cursor_line, &cursor_offset); if (cursor_line >= 0) { if (num_lines) { box[0].x = origin.x + infos[cursor_line].origin_x + cursor_offset - wi->text_info.insert_width/2; box[0].y = origin.y + infos[cursor_line].origin_y - fm.ascent + 1; } else { box[0].x = origin.x; box[0].y = origin.y; } box[2].x = box[0].x + wi->text_info.insert_width; box[2].y = box[0].y + font_height - 1; box[1].x = box[2].x; box[1].y = box[0].y; box[3].x = box[0].x; box[3].y = box[2].y; ZnTransformPoints(transfo, box, box, 4); ZnAddPointsToBBox(&item->item_bounding_box, box, 4); } } /*printf("bbox origin: %g %g corner %g %g\n", item->item_bounding_box.orig.x, item->item_bounding_box.orig.y, item->item_bounding_box.corner.x, item->item_bounding_box.corner.y);*/ /* * Update connected items. */ SET(item->flags, ZN_UPDATE_DEPENDENT_BIT); } /* ********************************************************************************** * * ToArea -- * Tell if the object is entirely outside (-1), * entirely inside (1) or in between (0). * ********************************************************************************** */ static int ToArea(ZnItem item, ZnToArea ta) { TextItem text = (TextItem) item; int inside = -1; ZnBool first_done = False; int num_lines, i; TextLineInfo lines, lines_ptr; Tk_FontMetrics fm; int font_height; ZnBBox line_bbox, *area = ta->area; ZnPoint box[4], p, origin; ZnTransfo inv, *transfo; if (!text->text_info || !text->text) { return -1; } transfo = ComputeTransfoAndOrigin(item, &origin); box[0] = area->orig; box[2] = area->corner; box[1].x = box[2].x; box[1].y = box[0].y; box[3].x = box[0].x; box[3].y = box[2].y; ZnTransfoInvert(transfo, &inv); ZnTransformPoints(&inv, box, box, 4); lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); Tk_GetFontMetrics(text->font, &fm); font_height = fm.descent + fm.ascent; if (text->spacing > 0) { font_height += text->spacing; } /*printf("text %d, num lines=%d\n", item->id, num_lines);*/ for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { ZnResetBBox(&line_bbox); p.x = origin.x + lines_ptr->origin_x; p.y = origin.y + lines_ptr->origin_y - fm.ascent; ZnAddPointToBBox(&line_bbox, p.x, p.y); ZnAddPointToBBox(&line_bbox, p.x + lines_ptr->width, p.y + font_height); if (!first_done) { first_done = True; inside = ZnPolygonInBBox(box, 4, &line_bbox, NULL); if (inside == 0) { return 0; } } else { if (ZnPolygonInBBox(box, 4, &line_bbox, NULL) == 0) { return 0; } } } return inside; } /* ********************************************************************************** * * Draw -- * ********************************************************************************** */ static void Draw(ZnItem item) { ZnWInfo *wi = item->wi; TextItem text = (TextItem) item; XGCValues values; ZnPoint pos, box[4], origin; ZnTransfo *transfo; Drawable drw; GC gc; XImage *src_im, *dest_im=NULL; unsigned int dest_im_width=0, dest_im_height=0; unsigned int gc_mask = 0; Tk_FontMetrics fm; unsigned int font_height; int num_lines, i; TextLineInfo lines, lines_ptr; ZnTextInfo *ti = &wi->text_info; unsigned int underline_thickness, underline_pos=0, overstrike_pos=0; int sel_first_line=-1, sel_last_line=-1, cursor_line=-1; unsigned int sel_start_offset=0, sel_stop_offset=0, cursor_offset=0; if (!text->text_info/* || !text->text*/) { return; } lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); Tk_GetFontMetrics(text->font, &fm); font_height = fm.ascent+fm.descent; transfo = ComputeTransfoAndOrigin(item, &origin); /* * Compute the selection and the cursor geometry. */ ComputeCursor(item, &cursor_line, &cursor_offset); ComputeSelection(item, &sel_first_line, &sel_last_line, &sel_start_offset, &sel_stop_offset); ZnTransformPoint(transfo, &origin, &pos); /*printf("sel 1st : %d offset : %d, sel last : %d offset : %d\n", sel_first_line, sel_start_offset, sel_last_line, sel_stop_offset);*/ /* * Setup the gc for the selection and fill the selection. */ if ((ti->sel_item == item) && (sel_first_line >= 0)) { XPoint xp[4]; values.foreground = ZnGetGradientPixel(ti->sel_color, 0.0); values.fill_style = FillSolid; XChangeGC(wi->dpy, wi->gc, GCFillStyle | GCForeground, &values); if (sel_first_line == sel_last_line) { box[0].x = origin.x + lines[sel_first_line].origin_x + sel_start_offset; box[0].y = origin.y + lines[sel_first_line].origin_y - fm.ascent; box[2].x = box[0].x + sel_stop_offset - sel_start_offset; box[2].y = box[0].y + font_height; box[1].x = box[2].x; box[1].y = box[0].y; box[3].x = box[0].x; box[3].y = box[2].y; ZnTransformPoints(transfo, box, box, 4); for (i = 0; i < 4; i++) { xp[i].x = (short) box[i].x; xp[i].y = (short) box[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin); } else { box[0].x = origin.x + lines[sel_first_line].origin_x + sel_start_offset; box[0].y = origin.y + lines[sel_first_line].origin_y - fm.ascent; box[2].x = box[0].x + text->max_width-lines[sel_first_line].origin_x-sel_start_offset; box[2].y = box[0].y + font_height; box[1].x = box[2].x; box[1].y = box[0].y; box[3].x = box[0].x; box[3].y = box[2].y; ZnTransformPoints(transfo, box, box, 4); for (i = 0; i < 4; i++) { xp[i].x = (short) box[i].x; xp[i].y = (short) box[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin); for (i = sel_first_line+1, lines_ptr = &lines[sel_first_line+1]; i < sel_last_line; i++, lines_ptr++) { box[0].x = origin.x; box[0].y = origin.y + lines_ptr->origin_y - fm.ascent; box[2].x = box[0].x + text->max_width; box[2].y = box[0].y + font_height; box[1].x = box[2].x; box[1].y = box[0].y; box[3].x = box[0].x; box[3].y = box[2].y; ZnTransformPoints(transfo, box, box, 4); for (i = 0; i < 4; i++) { xp[i].x = (short) box[i].x; xp[i].y = (short) box[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin); } box[0].x = origin.x; box[0].y = origin.y + lines[sel_last_line].origin_y - fm.ascent; box[2].x = box[0].x + lines[sel_last_line].origin_x + sel_stop_offset; box[2].y = box[0].y + font_height; box[1].x = box[2].x; box[1].y = box[0].y; box[3].x = box[0].x; box[3].y = box[2].y; ZnTransformPoints(transfo, box, box, 4); for (i = 0; i < 4; i++) { xp[i].x = (short) box[i].x; xp[i].y = (short) box[i].y; } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xp, 4, Convex, CoordModeOrigin); } } /*printf("cursor line : %d, cursor offset : %d\n", cursor_line, cursor_offset);*/ /* * Setup the gc for the cursor and draw it. */ if (cursor_line >= 0 && (wi->focus_item == item) && ti->cursor_on) { values.fill_style = FillSolid; values.line_width = ti->insert_width; values.foreground = ZnGetGradientPixel(ti->insert_color, 0.0); XChangeGC(wi->dpy, wi->gc, GCForeground|GCFillStyle|GCLineWidth, &values); box[0].x = origin.x + lines[cursor_line].origin_x + cursor_offset; box[0].y = origin.y + lines[cursor_line].origin_y - fm.ascent + 1; box[1].x = box[0].x; box[1].y = box[0].y + font_height - 1; ZnTransformPoints(transfo, box, box, 2); XDrawLine(wi->dpy, wi->draw_buffer, wi->gc, (int) box[0].x, (int) box[0].y, (int) box[1].x, (int) box[1].y); } /* * If the current transform is a pure translation, it is * possible to optimize by directly drawing to the X back * buffer. Else, we draw in a temporary buffer, get * its content as an image, transform the image into another * one and use this last image as a mask to draw in the X * back buffer. */ if (ZnTransfoIsTranslation(transfo)) { drw = wi->draw_buffer; gc = wi->gc; values.foreground = ZnGetGradientPixel(text->color, 0.0); } else { dest_im_width = (unsigned int) (item->item_bounding_box.corner.x - item->item_bounding_box.orig.x); dest_im_height = (unsigned int) (item->item_bounding_box.corner.y - item->item_bounding_box.orig.y); drw = Tk_GetPixmap(wi->dpy, wi->draw_buffer, MAX(dest_im_width, text->max_width), MAX(dest_im_height, text->height), 1); gc = XCreateGC(wi->dpy, drw, 0, NULL); XSetForeground(wi->dpy, gc, 0); XFillRectangle(wi->dpy, drw, gc, 0, 0, MAX(dest_im_width, text->max_width), MAX(dest_im_height, text->height)); dest_im = XCreateImage(wi->dpy, Tk_Visual(wi->win), 1, XYPixmap, 0, NULL, dest_im_width, dest_im_height, 8, 0); dest_im->data = ZnMalloc(dest_im->bytes_per_line * dest_im->height); memset(dest_im->data, 0, dest_im->bytes_per_line * dest_im->height); values.foreground = 1; pos.x = 0; pos.y = 0; } /* * Setup the gc to render the text and draw it. */ values.font = Tk_FontId(text->font); gc_mask = GCFont | GCForeground; if (text->fill_pattern != ZnUnspecifiedImage) { values.fill_style = FillStippled; values.stipple = ZnImagePixmap(text->fill_pattern, wi->win); gc_mask |= GCFillStyle | GCStipple; } else { values.fill_style = FillSolid; gc_mask |= GCFillStyle; } if (ISSET(text->flags, UNDERLINED) || ISSET(text->flags, OVERSTRIKED)) { /* * These 3 values should be fetched from the font. * Currently I don't know how without diving into * Tk internals. */ underline_thickness = 2; underline_pos = fm.descent/2; overstrike_pos = fm.ascent*3/10; values.line_style = LineSolid; values.line_width = underline_thickness; gc_mask |= GCLineStyle | GCLineWidth; } XChangeGC(wi->dpy, gc, gc_mask, &values); for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { int tmp_x, tmp_y; tmp_x = (int)(pos.x + lines_ptr->origin_x); tmp_y = (int)(pos.y + lines_ptr->origin_y); Tk_DrawChars(wi->dpy, drw, gc, text->font, (char *) lines_ptr->start, (int) lines_ptr->num_bytes, tmp_x, tmp_y); if (ISSET(text->flags, UNDERLINED)) { int y_under = tmp_y + underline_pos; XDrawLine(wi->dpy, drw, gc, tmp_x, y_under, tmp_x + (int) lines_ptr->width, y_under); } if (ISSET(text->flags, OVERSTRIKED)) { int y_over = tmp_y-overstrike_pos; XDrawLine(wi->dpy, drw, gc, tmp_x, y_over, tmp_x + (int) lines_ptr->width, y_over); } } if (dest_im != NULL) { src_im = XGetImage(wi->dpy, drw, 0, 0, text->max_width, text->height, 1, XYPixmap); box[0].x = origin.x; box[0].y = origin.y; box[3].x = box[0].x + text->max_width; box[3].y = box[0].y + text->height; box[1].x = box[0].x; box[1].y = box[3].y; box[2].x = box[3].x; box[2].y = box[0].y; ZnTransformPoints(transfo, box, box, 4); for (i = 0; i < 4; i++) { box[i].x -= item->item_bounding_box.orig.x; box[i].y -= item->item_bounding_box.orig.y; box[i].x = ZnNearestInt(box[i].x); box[i].y = ZnNearestInt(box[i].y); } ZnMapImage(src_im, dest_im, box); TkPutImage(NULL, 0,wi->dpy, drw, gc, dest_im, 0, 0, 0, 0, dest_im_width, dest_im_height); values.foreground = ZnGetGradientPixel(text->color, 0.0); values.stipple = drw; values.ts_x_origin = (int) item->item_bounding_box.orig.x; values.ts_y_origin = (int) item->item_bounding_box.orig.y; values.fill_style = FillStippled; XChangeGC(wi->dpy, wi->gc, GCFillStyle|GCStipple|GCTileStipXOrigin|GCTileStipYOrigin|GCForeground, &values); XFillRectangle(wi->dpy, wi->draw_buffer, wi->gc, (int) item->item_bounding_box.orig.x, (int) item->item_bounding_box.orig.y, dest_im_width, dest_im_height); XFreeGC(wi->dpy, gc); Tk_FreePixmap(wi->dpy, drw); XDestroyImage(src_im); XDestroyImage(dest_im); } } /* ********************************************************************************** * * Render -- * ********************************************************************************** */ #ifdef GL static void Render(ZnItem item) { ZnWInfo *wi = item->wi; TextItem text = (TextItem) item; TextLineInfo lines, lines_ptr; ZnTextInfo *ti = &wi->text_info; ZnPoint o, c, origin; ZnTransfo *transfo; GLdouble m[16]; int i, num_lines; XColor *color; unsigned short alpha; Tk_FontMetrics fm; int font_height; int underline_thickness, underline_pos=0, overstrike_pos=0; int sel_first_line=-1, sel_last_line=-1, cursor_line=-1; int sel_start_offset=0, sel_stop_offset=0, cursor_offset=0; if (!text->text_info) { return; } #ifdef GL_LIST if (!item->gl_list) { item->gl_list = glGenLists(1); glNewList(item->gl_list, GL_COMPILE); #endif lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); Tk_GetFontMetrics(text->font, &fm); font_height = fm.ascent+fm.descent; /* * These 3 values should be fetched from the font. * Currently I don't know how without diving into * Tk internals. */ underline_thickness = 2; underline_pos = fm.descent/2; overstrike_pos = fm.ascent*3/10; transfo = ComputeTransfoAndOrigin(item, &origin); /* * Compute the selection and the cursor geometry. */ ComputeCursor(item, &cursor_line, &cursor_offset); ComputeSelection(item, &sel_first_line, &sel_last_line, &sel_start_offset, &sel_stop_offset); ZnGLMakeCurrent(wi->dpy, wi); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPushMatrix(); memset(m, 0, sizeof(m)); m[0] = m[5] = m[15] = 1.0; if (transfo) { m[0] = transfo->_[0][0]; m[1] = transfo->_[0][1]; m[4] = transfo->_[1][0]; m[5] = transfo->_[1][1]; m[12] = ZnNearestInt(transfo->_[2][0]); m[13] = ZnNearestInt(transfo->_[2][1]); } glLoadMatrixd(m); glTranslated(origin.x, origin.y, 0.0); glPushMatrix(); /* * Render the selection. */ if ((ti->sel_item == item) && (sel_first_line >= 0)) { color = ZnGetGradientColor(ti->sel_color, 0.0, &alpha); alpha = ZnComposeAlpha(alpha, wi->alpha); glColor4us(color->red, color->green, color->blue, alpha); o.x = lines[sel_first_line].origin_x + sel_start_offset; o.y = lines[sel_first_line].origin_y - fm.ascent; glBegin(GL_QUADS); if (sel_first_line == sel_last_line) { c.x = o.x + sel_stop_offset - sel_start_offset; c.y = o.y + font_height; glVertex2d(o.x, o.y); glVertex2d(o.x, c.y); glVertex2d(c.x, c.y); glVertex2d(c.x, o.y); } else { c.x = o.x + (text->max_width - lines[sel_first_line].origin_x - sel_start_offset); c.y = o.y + font_height; glVertex2d(o.x, o.y); glVertex2d(o.x, c.y); glVertex2d(c.x, c.y); glVertex2d(c.x, o.y); for (i = sel_first_line+1, lines_ptr = &lines[sel_first_line+1]; i < sel_last_line; i++, lines_ptr++) { o.x = 0; o.y = lines_ptr->origin_y - fm.ascent; c.x = o.x + text->max_width; c.y = o.y + font_height; glVertex2d(o.x, o.y); glVertex2d(o.x, c.y); glVertex2d(c.x, c.y); glVertex2d(c.x, o.y); } o.x = 0; o.y = lines[sel_last_line].origin_y - fm.ascent; c.x = o.x + lines[sel_last_line].origin_x + sel_stop_offset; c.y = o.y + font_height; glVertex2d(o.x, o.y); glVertex2d(o.x, c.y); glVertex2d(c.x, c.y); glVertex2d(c.x, o.y); } glEnd(); } /* * Render the cursor. */ if ((cursor_line >= 0) && (wi->focus_item == item) && ti->cursor_on) { color = ZnGetGradientColor(ti->insert_color, 0.0, &alpha); alpha = ZnComposeAlpha(alpha, wi->alpha); glColor4us(color->red, color->green, color->blue, alpha); glLineWidth((GLfloat) ti->insert_width); o.x = lines[cursor_line].origin_x + cursor_offset; o.y = lines[cursor_line].origin_y - fm.ascent + 1; c.x = o.x; c.y = o.y + font_height - 1; glBegin(GL_LINES); glVertex2d(o.x, o.y); glVertex2d(c.x, c.y); glEnd(); } /* * Render the text. */ glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glBindTexture(GL_TEXTURE_2D, ZnTexFontTex(text->tfi)); color = ZnGetGradientColor(text->color, 0.0, &alpha); alpha = ZnComposeAlpha(alpha, wi->alpha); glColor4us(color->red, color->green, color->blue, alpha); for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { glTranslated(lines_ptr->origin_x, lines_ptr->origin_y, 0.0); if (ISSET(text->flags, UNDERLINED) || ISSET(text->flags, OVERSTRIKED)) { glLineWidth((GLfloat) underline_thickness); glDisable(GL_TEXTURE_2D); if (ISSET(text->flags, UNDERLINED)) { glBegin(GL_LINES); glVertex2d(0, underline_pos); glVertex2d(lines_ptr->width, underline_pos); glEnd(); } if (ISSET(text->flags, OVERSTRIKED)) { glBegin(GL_LINES); glVertex2d(0, -overstrike_pos); glVertex2d(lines_ptr->width, -overstrike_pos); glEnd(); } glEnable(GL_TEXTURE_2D); } ZnRenderString(text->tfi, lines_ptr->start, lines_ptr->num_bytes); glPopMatrix(); glPushMatrix(); } glPopMatrix(); glPopMatrix(); glDisable(GL_TEXTURE_2D); #ifdef GL_LIST glEndList(); } glCallList(item->gl_list); #endif } #else static void Render(ZnItem item) { } #endif /* ********************************************************************************** * * IsSensitive -- * ********************************************************************************** */ static ZnBool IsSensitive(ZnItem item, int item_part) { return (ISSET(item->flags, ZN_SENSITIVE_BIT) && item->parent->class->IsSensitive(item->parent, ZN_NO_PART)); } /* ********************************************************************************** * * Pick -- * ********************************************************************************** */ static double Pick(ZnItem item, ZnPick ps) { TextItem text = (TextItem) item; double dist = 1.0e40, new_dist; int num_lines, i; TextLineInfo lines, lines_ptr; Tk_FontMetrics fm; int font_height; ZnPoint box[4], origin, *p = ps->point; ZnTransfo *transfo; if (!text->text_info || !text->text) { return dist; } transfo = ComputeTransfoAndOrigin(item, &origin); lines = ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); Tk_GetFontMetrics(text->font, &fm); font_height = fm.descent + fm.ascent; if (text->spacing > 0) { font_height += text->spacing; } for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { box[0].x = origin.x + lines_ptr->origin_x; box[0].y = origin.y + lines_ptr->origin_y - fm.ascent; box[2].x = box[0].x + lines_ptr->width; box[2].y = box[0].y + font_height; box[1].x = box[2].x; box[1].y = box[0].y; box[3].x = box[0].x; box[3].y = box[2].y; ZnTransformPoints(transfo, box, box, 4); new_dist = ZnPolygonToPointDist(box, 4, p); dist = MIN(dist, new_dist); if (dist <= 0.0) { dist = 0.0; break; } } return dist; } /* ********************************************************************************** * * PostScript -- * ********************************************************************************** */ static int PostScript(ZnItem item, ZnBool prepass, ZnBBox *area) { ZnWInfo *wi = item->wi; TextItem text = (TextItem) item; Tk_FontMetrics fm; TextLineInfo lines, lines_ptr; ZnPoint origin; ZnReal alignment; int i, num_lines; char path[150]; lines = (TextLineInfo) ZnListArray(text->text_info); num_lines = ZnListSize(text->text_info); if (Tk_PostscriptFont(wi->interp, wi->ps_info, text->font) != TCL_OK) { return TCL_ERROR; } if (Tk_PostscriptColor(wi->interp, wi->ps_info, ZnGetGradientColor(text->color, 0.0, NULL)) != TCL_OK) { return TCL_ERROR; } if (text->fill_pattern != ZnUnspecifiedImage) { Tcl_AppendResult(wi->interp, "/StippleText {\n ", NULL); Tk_PostscriptStipple(wi->interp, wi->win, wi->ps_info, ZnImagePixmap(text->fill_pattern, wi->win)); Tcl_AppendResult(wi->interp, "} bind def\n", NULL); } ComputeTransfoAndOrigin(item, &origin); sprintf(path, "/InitialTransform load setmatrix\n" "[%.15g %.15g %.15g %.15g %.15g %.15g] concat\n" "1 -1 scale\n", wi->current_transfo->_[0][0], wi->current_transfo->_[0][1], wi->current_transfo->_[1][0], wi->current_transfo->_[1][1], wi->current_transfo->_[2][0], wi->current_transfo->_[2][1]); Tcl_AppendResult(wi->interp, path, NULL); sprintf(path, "%.15g %.15g [\n", origin.x, origin.y); Tcl_AppendResult(wi->interp, path, NULL); /* * Emit code to draw the lines. */ for (i = 0, lines_ptr = lines; i < num_lines; i++, lines_ptr++) { ZnPostscriptString(wi->interp, lines_ptr->start, lines_ptr->num_bytes); } switch (text->alignment) { default: case TK_JUSTIFY_LEFT: alignment = 0; break; case TK_JUSTIFY_CENTER: alignment = 0.5; break; case TK_JUSTIFY_RIGHT: alignment = 1; break; } Tk_GetFontMetrics(text->font, &fm); /* DrawText should not mess with anchors, they are already accounted for */ sprintf(path, "] %d %g %g %g %s DrawText\n", fm.linespace, 0.0, 0.0, alignment, (text->fill_pattern == ZnUnspecifiedImage) ? "false" : "true"); Tcl_AppendResult(wi->interp, path, NULL); return TCL_OK; } /* ********************************************************************************** * * GetAnchor -- * ********************************************************************************** */ static void GetAnchor(ZnItem item, Tk_Anchor anchor, ZnPoint *p) { TextItem text = (TextItem) item; if (text->num_chars != 0) { ZnRectOrigin2Anchor(text->poly, anchor, p); } else { *p = *text->poly; } } /* ********************************************************************************** * * GetClipVertices -- * Get the clipping shape. * Never ever call ZnTriFree on the tristrip returned by GetClipVertices. * ********************************************************************************** */ static ZnBool GetClipVertices(ZnItem item, ZnTriStrip *tristrip) { TextItem text = (TextItem) item; ZnTriStrip1(tristrip, text->poly, 4, False); return False; } /* ********************************************************************************** * * Coords -- * Return or edit the item origin. This doesn't take care of * the possible attachment. The change will be effective at the * end of the attachment. * ********************************************************************************** */ static int Coords(ZnItem item, int contour, int index, int cmd, ZnPoint **pts, char **controls, unsigned int *num_pts) { TextItem text = (TextItem) item; if ((cmd == ZN_COORDS_ADD) || (cmd == ZN_COORDS_ADD_LAST) || (cmd == ZN_COORDS_REMOVE)) { Tcl_AppendResult(item->wi->interp, " texts can't add or remove vertices", NULL); return TCL_ERROR; } else if ((cmd == ZN_COORDS_REPLACE) || (cmd == ZN_COORDS_REPLACE_ALL)) { if (*num_pts == 0) { Tcl_AppendResult(item->wi->interp, " coords command need 1 point on texts", NULL); return TCL_ERROR; } text->pos = (*pts)[0]; ZnITEM.Invalidate(item, ZN_COORDS_FLAG); } else if ((cmd == ZN_COORDS_READ) || (cmd == ZN_COORDS_READ_ALL)) { *num_pts = 1; *pts = &text->pos; } return TCL_OK; } /* ********************************************************************************** * * Index -- * Parse a text index and return its value and a * error status (standard Tcl result). * ********************************************************************************** */ static int PointToChar(TextItem text, int x, int y) { int i, n, num_lines, dummy, byte_index; ZnPoint p; TextLineInfo ti; Tk_FontMetrics fm; ZnReal a, b; byte_index = 0; if (!text->text_info) { return 0; } p.x = x; p.y = y; a = ZnLineToPointDist(&text->poly[0], &text->poly[2], &p, NULL); b = ZnLineToPointDist(&text->poly[0], &text->poly[1], &p, NULL); p.x = (text->max_width * b / hypot(text->poly[2].x - text->poly[0].x, text->poly[2].y - text->poly[0].y)); p.y = (text->height * a / hypot(text->poly[1].x - text->poly[0].x, text->poly[1].y - text->poly[0].y)); p.x = ZnNearestInt(p.x); p.y = ZnNearestInt(p.y); /* * Point above text, returns index 0. */ if (p.y < 0) { return 0; } /* * Find the text line under point. */ num_lines = ZnListSize(text->text_info); ti = ZnListArray(text->text_info); Tk_GetFontMetrics(text->font, &fm); for (i = 0; i < num_lines; i++, ti++) { if (p.y < ti->origin_y + fm.descent) { if (p.x < ti->origin_x) { /* * Point to the left of the current line, returns * index of first char. */ byte_index = ti->start - text->text; break; } if (p.x >= (ti->origin_x + ti->width)) { /* * Point to the right of the current line, returns * index past the last char. */ byte_index = ti->start + ti->num_bytes - text->text; break; } n = Tk_MeasureChars(text->font, ti->start, (int) ti->num_bytes, (int) (p.x + 2 - ti->origin_x), TK_PARTIAL_OK, &dummy); #ifdef PTK_800 byte_index = (ti->start + n - 1) - text->text; #else byte_index = Tcl_UtfPrev(ti->start + n, ti->start) - text->text; #endif break; } } if (i == num_lines) { /* * Point below all lines, return the index after * the last char in text. */ ti--; byte_index = ti->start + ti->num_bytes - text->text; } return Tcl_NumUtfChars(text->text, byte_index); } /* * Return a new index from a current index and a * move command. * * 0 end of index line * 1 beginning of index line * 2 next word or end of word from index * 3 previous word or beginning of word from index * 4 previous line from index line * 5 next line from index line * */ static int MoveFromIndex(TextItem text, unsigned int char_index, int move) { unsigned int num_lines, byte_index, num_bytes=0; unsigned int line_index, line_start=0; TextLineInfo lines, p; char *strp; if (!text->text_info || !text->text) { return char_index; } byte_index = Tcl_UtfAtIndex(text->text, (int) char_index)-text->text; num_lines = ZnListSize(text->text_info); lines = p = ZnListArray(text->text_info); for (line_index = 0; line_index < num_lines; line_index++, p++) { line_start = p->start - text->text; num_bytes = p->num_bytes; if (line_start + num_bytes >= byte_index) { break; } } if (line_index == num_lines) { line_index--; p--; } switch (move) { case 0: byte_index = line_start + num_bytes; goto convert_it; case 1: byte_index = line_start; goto convert_it; case 2: strp = &text->text[byte_index]; while ((strp[1] == ' ') || (strp[1] == '\n')) { strp++; } while ((strp[1] != ' ') && (strp[1] != '\n') && strp[1]) { strp++; } byte_index = strp + 1 - text->text; goto convert_it; case 3: strp = &text->text[byte_index]; while ((strp != text->text) && ((strp[-1] == ' ') || (strp[-1] == '\n'))) { strp--; } while ((strp != text->text) && (strp[-1] != ' ') && (strp[-1] != '\n')) { strp--; } byte_index = strp - text->text; goto convert_it; case 4: if (line_index > 0) { byte_index -= line_start; p = &lines[line_index-1]; byte_index = MIN(byte_index, p->num_bytes); line_start = p->start - text->text; byte_index += line_start; } goto convert_it; case 5: if (line_index < num_lines-1) { byte_index -= line_start; p = &lines[line_index+1]; byte_index = MIN(byte_index, p->num_bytes); line_start = p->start - text->text; byte_index += line_start; } convert_it: char_index = Tcl_NumUtfChars(text->text, (int) byte_index); default: return char_index; } } static int Index(ZnItem item, int field, Tcl_Obj *index_spec, int *index) { TextItem text = (TextItem) item; ZnWInfo *wi = item->wi; ZnTextInfo *ti = &wi->text_info; unsigned int length; int c, x, y; double tmp; char *end, *p; p = Tcl_GetString(index_spec); c = p[0]; length = strlen(p); if ((c == 'e') && (length > 1) && (strncmp(p, "end", length) == 0)) { *index = text->num_chars; } else if ((c == 'e') && (length > 1) && (strncmp(p, "eol", length) == 0)) { *index = MoveFromIndex(text, text->insert_index, 0); } else if ((c == 'b') && (length > 1) && (strncmp(p, "bol", length) == 0)) { *index = MoveFromIndex(text, text->insert_index, 1); } else if ((c == 'e') && (length > 1) && (strncmp(p, "eow", length) == 0)) { *index = MoveFromIndex(text, text->insert_index, 2); } else if ((c == 'b') && (length > 1) && (strncmp(p, "bow", length) == 0)) { *index = MoveFromIndex(text, text->insert_index, 3); } else if ((c == 'u') && (strncmp(p, "up", length) == 0)) { *index = MoveFromIndex(text, text->insert_index, 4); } else if ((c == 'd') && (strncmp(p, "down", length) == 0)) { *index = MoveFromIndex(text, text->insert_index, 5); } else if ((c == 'i') && (strncmp(p, "insert", length) == 0)) { *index = text->insert_index; } else if ((c == 's') && (strncmp(p, "sel.first", length) == 0) && (length >= 5)) { if (ti->sel_item != item) { Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL); return TCL_ERROR; } *index = ti->sel_first; } else if ((c == 's') && (strncmp(p, "sel.last", length) == 0) && (length >= 5)) { if (ti->sel_item != item) { Tcl_AppendResult(wi->interp, "selection isn't in item", (char *) NULL); return TCL_ERROR; } /* * We return a modified selection end so that it reflect * the text index of the last character _not_ the insertion * point between the last and the next. */ *index = ti->sel_last-1; } else if (c == '@') { p++; tmp = strtod(p, &end); if ((end == p) || (*end != ',')) { goto badIndex; } /*x = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);*/ x = (int) tmp; p = end+1; tmp = strtod(p, &end); if ((end == p) || (*end != 0)) { goto badIndex; } /*y = (int) ((tmp < 0) ? tmp - 0.5 : tmp + 0.5);*/ y = (int) tmp; *index = PointToChar(text, x, y); } else if (Tcl_GetIntFromObj(wi->interp, index_spec, index) == TCL_OK) { if (*index < 0){ *index = 0; } else if ((unsigned int) *index > text->num_chars) { *index = text->num_chars; } } else { badIndex: Tcl_AppendResult(wi->interp, "bad index \"", p, "\"", (char *) NULL); return TCL_ERROR; } return TCL_OK; } /* ********************************************************************************** * * InsertChars -- * ********************************************************************************** */ static void InsertChars(ZnItem item, int field, int *index, char *chars) { TextItem text = (TextItem) item; ZnTextInfo *ti = &item->wi->text_info; unsigned int num_chars, byte_index, num_bytes = strlen(chars); char *new; if (num_bytes == 0) { return; } if (*index < 0) { *index = 0; } if ((unsigned int) *index > text->num_chars) { *index = text->num_chars; } num_chars = Tcl_NumUtfChars(chars, (int) num_bytes); if (text->text) { byte_index = Tcl_UtfAtIndex(text->text, *index)-text->text; new = ZnMalloc(strlen(text->text) + num_bytes + 1); memcpy(new, text->text, (size_t) byte_index); strcpy(new + byte_index + num_bytes, text->text + byte_index); ZnFree(text->text); } else { byte_index = 0; new = ZnMalloc(num_bytes + 1); new[num_bytes] = 0; } memcpy(new + byte_index, chars, num_bytes); text->text = new; text->num_chars += num_chars; if (text->insert_index >= (unsigned int) *index) { text->insert_index += num_chars; } if (ti->sel_item == item) { if (ti->sel_first >= *index) { ti->sel_first += num_chars; } if (ti->sel_last >= *index) { ti->sel_last += num_chars; } if ((ti->anchor_item == item) && (ti->sel_anchor >= *index)) { ti->sel_anchor += num_chars; } } ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG); } /* ********************************************************************************** * * DeleteChars -- * ********************************************************************************** */ static void DeleteChars(ZnItem item, int field, int *first, int *last) { TextItem text = (TextItem) item; int byte_count, first_offset; int char_count, num_bytes; ZnTextInfo *ti = &item->wi->text_info; char *new; if (!text->text) { return; } if (*first < 0) { *first = 0; } if (*last >= (int) text->num_chars) { *last = text->num_chars-1; } if (*first > *last) { return; } char_count = *last + 1 - *first; first_offset = Tcl_UtfAtIndex(text->text, *first)-text->text; byte_count = Tcl_UtfAtIndex(text->text + first_offset, char_count)- (text->text+first_offset); num_bytes = strlen(text->text); if (num_bytes - byte_count) { new = (char *) ZnMalloc((unsigned) (num_bytes + 1 - byte_count)); memcpy(new, text->text, (size_t) first_offset); strcpy(new + first_offset, text->text + first_offset + byte_count); ZnFree(text->text); text->text = new; text->num_chars -= char_count; } else { ZnFree(text->text); text->text = NULL; text->num_chars = 0; } if (text->insert_index > (unsigned int) *first) { text->insert_index -= char_count; if (text->insert_index < (unsigned int) *first) { text->insert_index = *first; } else if (*first == 0) { text->insert_index = 0; } } if (ti->sel_item == item) { if (ti->sel_first > *first) { ti->sel_first -= char_count; if (ti->sel_first < *first) { ti->sel_first = *first; } } if (ti->sel_last >= *first) { ti->sel_last -= char_count; if (ti->sel_last < *first - 1) { ti->sel_last = *first - 1; } } if (ti->sel_first > ti->sel_last) { ti->sel_item = ZN_NO_ITEM; } if ((ti->anchor_item == item) && (ti->sel_anchor > *first)) { ti->sel_anchor -= char_count; if (ti->sel_anchor < *first) { ti->sel_anchor = *first; } } } ZnITEM.Invalidate(item, ZN_COORDS_FLAG|ZN_LAYOUT_FLAG); } /* ********************************************************************************** * * Cursor -- * ********************************************************************************** */ static void TextCursor(ZnItem item, int field, int index) { TextItem text = (TextItem) item; if (index < 0) { text->insert_index = 0; } else if ((unsigned int) index > text->num_chars) { text->insert_index = text->num_chars; } else { text->insert_index = index; } } /* ********************************************************************************** * * Selection -- * ********************************************************************************** */ static int Selection(ZnItem item, int field, int offset, char *chars, int max_bytes) { TextItem text = (TextItem) item; ZnWInfo *wi = item->wi; ZnTextInfo *ti = &wi->text_info; int count; char const *sel_first, *sel_last; if (!text->text) { return 0; } if ((ti->sel_first < 0) || (ti->sel_first > ti->sel_last)) { return 0; } sel_first = Tcl_UtfAtIndex(text->text, ti->sel_first); sel_last = Tcl_UtfAtIndex(sel_first, ti->sel_last + 1 - ti->sel_first); count = sel_last - sel_first - offset; if (count <= 0) { return 0; } if (count > max_bytes) { count = max_bytes; } memcpy(chars, sel_first + offset, (size_t) count); chars[count] = 0; return count; } /* ********************************************************************************** * * Exported functions struct * ********************************************************************************** */ static ZnItemClassStruct TEXT_ITEM_CLASS = { "text", sizeof(TextItemStruct), text_attrs, 0, /* num_parts */ ZN_CLASS_HAS_ANCHORS|ZN_CLASS_ONE_COORD, /* flags */ Tk_Offset(TextItemStruct, pos), Init, Clone, Destroy, Configure, Query, NULL, /* GetFieldSet */ GetAnchor, GetClipVertices, NULL, /* GetContours */ Coords, InsertChars, DeleteChars, TextCursor, Index, NULL, /* Part */ Selection, NULL, /* Contour */ ComputeCoordinates, ToArea, Draw, Render, IsSensitive, Pick, NULL, /* PickVertex */ PostScript }; ZnItemClassId ZnText = (ZnItemClassId) &TEXT_ITEM_CLASS;