/* * tkFont.c -- * * This file maintains a database of fonts for the Tk toolkit. It also * provides several utility functions for measuring and displaying text. * * Copyright © 1990-1994 The Regents of the University of California. * Copyright © 1994-1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tkInt.h" #include "tkFont.h" #if defined(MAC_OSX_TK) #include "tkMacOSXInt.h" /* Defines TK_DRAW_IN_CONTEXT */ #endif #ifdef _WIN32 #include "tkWinInt.h" #endif /* * The following structure is used to keep track of all the fonts that exist * in the current application. It must be stored in the TkMainInfo for the * application. */ typedef struct TkFontInfo { Tcl_HashTable fontCache; /* Map a string to an existing Tk_Font. Keys * are string font names, values are TkFont * pointers. */ Tcl_HashTable namedTable; /* Map a name to a set of attributes for a * font, used when constructing a Tk_Font from * a named font description. Keys are strings, * values are NamedFont pointers. */ TkMainInfo *mainPtr; /* Application that owns this structure. */ int updatePending; /* Non-zero when a World Changed event has * already been queued to handle a change to a * named font. */ } TkFontInfo; /* * The following data structure is used to keep track of the font attributes * for each named font that has been defined. The named font is only deleted * when the last reference to it goes away. */ typedef struct NamedFont { size_t refCount; /* Number of users of named font. */ int deletePending; /* Non-zero if font should be deleted when * last reference goes away. */ TkFontAttributes fa; /* Desired attributes for named font. */ } NamedFont; /* * The following two structures are used to keep track of string measurement * information when using the text layout facilities. * * A LayoutChunk represents a contiguous range of text that can be measured * and displayed by low-level text calls. In general, chunks will be delimited * by newlines and tabs. Low-level, platform-specific things like kerning and * non-integer character widths may occur between the characters in a single * chunk, but not between characters in different chunks. * * A TextLayout is a collection of LayoutChunks. It can be displayed with * respect to any origin. It is the implementation of the Tk_TextLayout opaque * token. */ typedef struct LayoutChunk { const char *start; /* Pointer to simple string to be displayed. * This is a pointer into the TkTextLayout's * string. */ Tcl_Size numBytes; /* The number of bytes in this chunk. */ Tcl_Size numChars; /* The number of characters in this chunk. */ Tcl_Size numDisplayChars; /* The number of characters to display when * this chunk is displayed. Can be less than * numChars if extra space characters were * absorbed by the end of the chunk. This will * be < 0 if this is a chunk that is holding a * tab or newline. */ int x, y; /* The origin of the first character in this * chunk with respect to the upper-left hand * corner of the TextLayout. */ int totalWidth; /* Width in pixels of this chunk. Used when * hit testing the invisible spaces at the end * of a chunk. */ int displayWidth; /* Width in pixels of the displayable * characters in this chunk. Can be less than * width if extra space characters were * absorbed by the end of the chunk. */ } LayoutChunk; typedef struct TextLayout { Tk_Font tkfont; /* The font used when laying out the text. */ const char *string; /* The string that was layed out. */ int width; /* The maximum width of all lines in the text * layout. */ Tcl_Size numChunks; /* Number of chunks actually used in following * array. */ LayoutChunk chunks[TKFLEXARRAY];/* Array of chunks. The actual size will be * maxChunks. THIS FIELD MUST BE THE LAST IN * THE STRUCTURE. */ } TextLayout; /* * The following structures are used as two-way maps between the values for * the fields in the TkFontAttributes structure and the strings used in Tcl, * when parsing both option-value format and style-list format font name * strings. */ static const TkStateMap weightMap[] = { {TK_FW_NORMAL, "normal"}, {TK_FW_BOLD, "bold"}, {TK_FW_UNKNOWN, NULL} }; static const TkStateMap slantMap[] = { {TK_FS_ROMAN, "roman"}, {TK_FS_ITALIC, "italic"}, {TK_FS_UNKNOWN, NULL} }; static const TkStateMap underlineMap[] = { {1, "underline"}, {0, NULL} }; static const TkStateMap overstrikeMap[] = { {1, "overstrike"}, {0, NULL} }; /* * The following structures are used when parsing XLFD's into a set of * TkFontAttributes. */ static const TkStateMap xlfdWeightMap[] = { {TK_FW_NORMAL, "normal"}, {TK_FW_NORMAL, "medium"}, {TK_FW_NORMAL, "book"}, {TK_FW_NORMAL, "light"}, {TK_FW_BOLD, "bold"}, {TK_FW_BOLD, "demi"}, {TK_FW_BOLD, "demibold"}, {TK_FW_NORMAL, NULL} /* Assume anything else is "normal". */ }; static const TkStateMap xlfdSlantMap[] = { {TK_FS_ROMAN, "r"}, {TK_FS_ITALIC, "i"}, {TK_FS_OBLIQUE, "o"}, {TK_FS_ROMAN, NULL} /* Assume anything else is "roman". */ }; static const TkStateMap xlfdSetwidthMap[] = { {TK_SW_NORMAL, "normal"}, {TK_SW_CONDENSE, "narrow"}, {TK_SW_CONDENSE, "semicondensed"}, {TK_SW_CONDENSE, "condensed"}, {TK_SW_UNKNOWN, NULL} }; /* * The following structure and defines specify the valid builtin options when * configuring a set of font attributes. */ static const char *const fontOpt[] = { "-family", "-size", "-weight", "-slant", "-underline", "-overstrike", NULL }; #define FONT_FAMILY 0 #define FONT_SIZE 1 #define FONT_WEIGHT 2 #define FONT_SLANT 3 #define FONT_UNDERLINE 4 #define FONT_OVERSTRIKE 5 #define FONT_NUMFIELDS 6 /* * Hardcoded font aliases. These are used to describe (mostly) identical fonts * whose names differ from platform to platform. If the user-supplied font * name matches any of the names in one of the alias lists, the other names in * the alias list are also automatically tried. */ static const char *const timesAliases[] = { "Times", /* Unix. */ "Times New Roman", /* Windows. */ "New York", /* Mac. */ NULL }; static const char *const helveticaAliases[] = { "Helvetica", /* Unix. */ "Arial", /* Windows. */ "Geneva", /* Mac. */ NULL }; static const char *const courierAliases[] = { "Courier", /* Unix and Mac. */ "Courier New", /* Windows. */ NULL }; static const char *const minchoAliases[] = { "mincho", /* Unix. */ "\357\274\255\357\274\263 \346\230\216\346\234\235", /* Windows (MS mincho). */ "\346\234\254\346\230\216\346\234\235\342\210\222\357\274\255", /* Mac (honmincho-M). */ NULL }; static const char *const gothicAliases[] = { "gothic", /* Unix. */ "\357\274\255\357\274\263 \343\202\264\343\202\267\343\203\203\343\202\257", /* Windows (MS goshikku). */ "\344\270\270\343\202\264\343\202\267\343\203\203\343\202\257\342\210\222\357\274\255", /* Mac (goshikku-M). */ NULL }; static const char *const dingbatsAliases[] = { "dingbats", "zapfdingbats", "itc zapfdingbats", /* Unix. */ /* Windows. */ "zapf dingbats", /* Mac. */ NULL }; static const char *const *const fontAliases[] = { timesAliases, helveticaAliases, courierAliases, minchoAliases, gothicAliases, dingbatsAliases, NULL }; /* * Hardcoded font classes. If the character cannot be found in the base font, * the classes are examined in order to see if some other similar font should * be examined also. */ static const char *const systemClass[] = { "fixed", /* Unix. */ /* Windows. */ "chicago", "osaka", "sistemny", /* Mac. */ NULL }; static const char *const serifClass[] = { "times", "palatino", "mincho", /* All platforms. */ "song ti", /* Unix. */ "ms serif", "simplified arabic", /* Windows. */ "latinski", /* Mac. */ NULL }; static const char *const sansClass[] = { "helvetica", "gothic", /* All platforms. */ /* Unix. */ "ms sans serif", "traditional arabic", /* Windows. */ "bastion", /* Mac. */ NULL }; static const char *const monoClass[] = { "courier", "gothic", /* All platforms. */ "fangsong ti", /* Unix. */ "simplified arabic fixed", /* Windows. */ "monaco", "pryamoy", /* Mac. */ NULL }; static const char *const symbolClass[] = { "symbol", "dingbats", "wingdings", NULL }; static const char *const *const fontFallbacks[] = { systemClass, serifClass, sansClass, monoClass, symbolClass, NULL }; /* * Global fallbacks. If the character could not be found in the preferred * fallback list, this list is examined. If the character still cannot be * found, all font families in the system are examined. */ static const char *const globalFontClass[] = { "symbol", /* All platforms. */ /* Unix. */ "lucida sans unicode", /* Windows. */ "bitstream cyberbit", /* Windows popular CJK font */ "chicago", /* Mac. */ NULL }; #define GetFontAttributes(tkfont) \ ((const TkFontAttributes *) &((TkFont *) (tkfont))->fa) #define GetFontMetrics(tkfont) \ ((const TkFontMetrics *) &((TkFont *) (tkfont))->fm) static int ConfigAttributesObj(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Size objc, Tcl_Obj *const objv[], TkFontAttributes *faPtr); static void DupFontObjProc(Tcl_Obj *srcObjPtr, Tcl_Obj *dupObjPtr); static int FieldSpecified(const char *field); static void FreeFontObj(Tcl_Obj *objPtr); static void FreeFontObjProc(Tcl_Obj *objPtr); static int GetAttributeInfoObj(Tcl_Interp *interp, const TkFontAttributes *faPtr, Tcl_Obj *objPtr); static LayoutChunk * NewChunk(TextLayout **layoutPtrPtr, int *maxPtr, const char *start, Tcl_Size numChars, int curX, int newX, int y); static int ParseFontNameObj(Tcl_Interp *interp, Tk_Window tkwin, Tcl_Obj *objPtr, TkFontAttributes *faPtr); static void RecomputeWidgets(TkWindow *winPtr); static int SetFontFromAny(Tcl_Interp *interp, Tcl_Obj *objPtr); static void TheWorldHasChanged(void *clientData); static void UpdateDependentFonts(TkFontInfo *fiPtr, Tk_Window tkwin, Tcl_HashEntry *namedHashPtr); /* * The following structure defines the implementation of the "font" Tcl * object, used for drawing. The internalRep.twoPtrValue.ptr1 field of each * font object points to the TkFont structure for the font, or NULL. */ const TkObjType tkFontObjType = { {"font", /* name */ FreeFontObjProc, /* freeIntRepProc */ DupFontObjProc, /* dupIntRepProc */ NULL, /* updateStringProc */ NULL, /* setFromAnyProc */ TCL_OBJTYPE_V0}, 0 }; /* *--------------------------------------------------------------------------- * * TkFontPkgInit -- * * This function is called when an application is created. It initializes * all the structures that are used by the font package on a per * application basis. * * Results: * Stores a token in the mainPtr to hold information needed by this * package on a per application basis. * * Side effects: * Memory allocated. * *--------------------------------------------------------------------------- */ void TkFontPkgInit( TkMainInfo *mainPtr) /* The application being created. */ { TkFontInfo *fiPtr = (TkFontInfo *)ckalloc(sizeof(TkFontInfo)); Tcl_InitHashTable(&fiPtr->fontCache, TCL_STRING_KEYS); Tcl_InitHashTable(&fiPtr->namedTable, TCL_STRING_KEYS); fiPtr->mainPtr = mainPtr; fiPtr->updatePending = 0; mainPtr->fontInfoPtr = fiPtr; TkpFontPkgInit(mainPtr); } /* *--------------------------------------------------------------------------- * * TkFontPkgFree -- * * This function is called when an application is deleted. It deletes all * the structures that were used by the font package for this * application. * * Results: * None. * * Side effects: * Memory freed. * *--------------------------------------------------------------------------- */ void TkFontPkgFree( TkMainInfo *mainPtr) /* The application being deleted. */ { TkFontInfo *fiPtr = mainPtr->fontInfoPtr; Tcl_HashEntry *hPtr, *searchPtr; Tcl_HashSearch search; #ifdef PURIFY int fontsLeft = 0; #endif for (searchPtr = Tcl_FirstHashEntry(&fiPtr->fontCache, &search); searchPtr != NULL; searchPtr = Tcl_NextHashEntry(&search)) { #ifdef PURIFY fontsLeft++; #endif #ifdef DEBUG_FONTS fprintf(stderr, "Font %s still in cache.\n", (char *) Tcl_GetHashKey(&fiPtr->fontCache, searchPtr)); #endif } #ifdef PURIFY if (fontsLeft) { Tcl_Panic("TkFontPkgFree: all fonts should have been freed already"); } #endif Tcl_DeleteHashTable(&fiPtr->fontCache); hPtr = Tcl_FirstHashEntry(&fiPtr->namedTable, &search); while (hPtr != NULL) { ckfree(Tcl_GetHashValue(hPtr)); hPtr = Tcl_NextHashEntry(&search); } Tcl_DeleteHashTable(&fiPtr->namedTable); if (fiPtr->updatePending) { Tcl_CancelIdleCall(TheWorldHasChanged, fiPtr); } ckfree(fiPtr); } /* *--------------------------------------------------------------------------- * * Tk_FontObjCmd -- * * This function is implemented to process the "font" Tcl command. See * the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *---------------------------------------------------------------------- */ int Tk_FontObjCmd( void *clientData, /* Main window associated with interpreter. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ Tcl_Obj *const objv[]) /* Argument objects. */ { int index; Tk_Window tkwin = (Tk_Window)clientData; TkFontInfo *fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr; static const char *const optionStrings[] = { "actual", "configure", "create", "delete", "families", "measure", "metrics", "names", NULL }; enum options { FONT_ACTUAL, FONT_CONFIGURE, FONT_CREATE, FONT_DELETE, FONT_FAMILIES, FONT_MEASURE, FONT_METRICS, FONT_NAMES }; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch ((enum options) index) { case FONT_ACTUAL: { int skip, result, n; const char *s; Tk_Font tkfont; Tcl_Obj *optPtr, *charPtr, *resultPtr; int uniChar = 0; const TkFontAttributes *faPtr; TkFontAttributes fa; /* * Params 0 and 1 are 'font actual'. Param 2 is the font name. 3-4 may * be '-displayof $window' */ skip = TkGetDisplayOf(interp, objc - 3, objv + 3, &tkwin); if (skip < 0) { return TCL_ERROR; } /* * Next parameter may be an option. */ n = 3 + skip; optPtr = NULL; charPtr = NULL; if (n < objc) { s = Tcl_GetString(objv[n]); if (s[0] == '-' && s[1] != '-') { optPtr = objv[n]; n++; } else { optPtr = NULL; } } /* * Next parameter may be '--' to mark end of options. */ if (n < objc) { if (!strcmp(Tcl_GetString(objv[n]), "--")) { n++; } } /* * Next parameter is the character to get font information for. */ if (n < objc) { charPtr = objv[n]; n++; } /* * If there were fewer than 3 args, or args remain, that's an error. */ if (objc < 3 || n < objc) { Tcl_WrongNumArgs(interp, 2, objv, "font ?-displayof window? ?-option? ?--? ?char?"); return TCL_ERROR; } /* * The 'charPtr' arg must be a single Unicode. */ if (charPtr != NULL) { const char *string = Tcl_GetString(charPtr); size_t len = Tcl_UtfToUniChar(string, &uniChar); if (len != (size_t)charPtr->length) { resultPtr = Tcl_NewStringObj( "expected a single character but got \"", TCL_INDEX_NONE); Tcl_AppendLimitedToObj(resultPtr, string, TCL_INDEX_NONE, 40, "..."); Tcl_AppendToObj(resultPtr, "\"", TCL_INDEX_NONE); Tcl_SetObjResult(interp, resultPtr); Tcl_SetErrorCode(interp, "TK", "VALUE", "FONT_SAMPLE", NULL); return TCL_ERROR; } } /* * Find the font. */ tkfont = Tk_AllocFontFromObj(interp, tkwin, objv[2]); if (tkfont == NULL) { return TCL_ERROR; } /* * Determine the font attributes. */ if (charPtr == NULL) { faPtr = GetFontAttributes(tkfont); } else { TkpGetFontAttrsForChar(tkwin, tkfont, uniChar, &fa); faPtr = &fa; } result = GetAttributeInfoObj(interp, faPtr, optPtr); Tk_FreeFont(tkfont); return result; } case FONT_CONFIGURE: { int result; const char *string; Tcl_Obj *objPtr; NamedFont *nfPtr; Tcl_HashEntry *namedHashPtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "fontname ?-option value ...?"); return TCL_ERROR; } string = Tcl_GetString(objv[2]); namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable, string); nfPtr = NULL; if (namedHashPtr != NULL) { nfPtr = (NamedFont *)Tcl_GetHashValue(namedHashPtr); } if ((namedHashPtr == NULL) || nfPtr->deletePending) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "named font \"%s\" doesn't exist", string)); Tcl_SetErrorCode(interp, "TK", "LOOKUP", "FONT", string, NULL); return TCL_ERROR; } if (objc == 3) { objPtr = NULL; } else if (objc == 4) { objPtr = objv[3]; } else { result = ConfigAttributesObj(interp, tkwin, objc - 3, objv + 3, &nfPtr->fa); UpdateDependentFonts(fiPtr, tkwin, namedHashPtr); return result; } return GetAttributeInfoObj(interp, &nfPtr->fa, objPtr); } case FONT_CREATE: { int skip = 3, i; const char *name; char buf[16 + TCL_INTEGER_SPACE]; TkFontAttributes fa; Tcl_HashEntry *namedHashPtr; if (objc < 3) { name = NULL; } else { name = Tcl_GetString(objv[2]); if (name[0] == '-') { name = NULL; } } if (name == NULL) { /* * No font name specified. Generate one of the form "fontX". */ for (i = 1; ; i++) { snprintf(buf, sizeof(buf), "font%d", i); namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable, buf); if (namedHashPtr == NULL) { break; } } name = buf; skip = 2; } TkInitFontAttributes(&fa); if (ConfigAttributesObj(interp, tkwin, objc - skip, objv + skip, &fa) != TCL_OK) { return TCL_ERROR; } if (TkCreateNamedFont(interp, tkwin, name, &fa) != TCL_OK) { return TCL_ERROR; } Tcl_SetObjResult(interp, Tcl_NewStringObj(name, TCL_INDEX_NONE)); break; } case FONT_DELETE: { int i, result = TCL_OK; const char *string; /* * Delete the named font. If there are still widgets using this font, * then it isn't deleted right away. */ if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "fontname ?fontname ...?"); return TCL_ERROR; } for (i = 2; (i < objc) && (result == TCL_OK); i++) { string = Tcl_GetString(objv[i]); result = TkDeleteNamedFont(interp, tkwin, string); } return result; } case FONT_FAMILIES: { int skip = TkGetDisplayOf(interp, objc - 2, objv + 2, &tkwin); if (skip < 0) { return TCL_ERROR; } if (objc != 2 + skip) { Tcl_WrongNumArgs(interp, 2, objv, "?-displayof window?"); return TCL_ERROR; } TkpGetFontFamilies(interp, tkwin); break; } case FONT_MEASURE: { const char *string; Tk_Font tkfont; Tcl_Size length = 0; int skip = 0; if (objc > 4) { skip = TkGetDisplayOf(interp, objc - 3, objv + 3, &tkwin); if (skip < 0) { return TCL_ERROR; } } if (objc != 4 + skip) { Tcl_WrongNumArgs(interp, 2, objv, "font ?-displayof window? text"); return TCL_ERROR; } tkfont = Tk_AllocFontFromObj(interp, tkwin, objv[2]); if (tkfont == NULL) { return TCL_ERROR; } string = Tcl_GetStringFromObj(objv[3 + skip], &length); Tcl_SetObjResult(interp, Tcl_NewWideIntObj( Tk_TextWidth(tkfont, string, length))); Tk_FreeFont(tkfont); break; } case FONT_METRICS: { Tk_Font tkfont; int skip, i; const TkFontMetrics *fmPtr; static const char *const switches[] = { "-ascent", "-descent", "-fixed", "-linespace", NULL }; skip = TkGetDisplayOf(interp, objc - 3, objv + 3, &tkwin); if (skip < 0) { return TCL_ERROR; } if ((objc < 3) || (objc > 4 + skip)) { Tcl_WrongNumArgs(interp, 2, objv, "font ?-displayof window? ?-option?"); return TCL_ERROR; } tkfont = Tk_AllocFontFromObj(interp, tkwin, objv[2]); if (tkfont == NULL) { return TCL_ERROR; } objc -= skip; objv += skip; fmPtr = GetFontMetrics(tkfont); if (objc == 3) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "-ascent %d -descent %d -linespace %d -fixed %d", fmPtr->ascent, fmPtr->descent, fmPtr->ascent + fmPtr->descent, fmPtr->fixed)); } else { if (Tcl_GetIndexFromObj(interp, objv[3], switches, "metric", 0, &index) != TCL_OK) { Tk_FreeFont(tkfont); return TCL_ERROR; } i = 0; /* Needed only to prevent compiler warning. */ switch (index) { case 0: i = fmPtr->ascent; break; case 1: i = fmPtr->descent; break; case 2: i = fmPtr->fixed; break; case 3: i = fmPtr->ascent + fmPtr->descent; break; } Tcl_SetObjResult(interp, Tcl_NewWideIntObj(i)); } Tk_FreeFont(tkfont); break; } case FONT_NAMES: { Tcl_HashSearch search; Tcl_HashEntry *namedHashPtr; Tcl_Obj *resultPtr; if (objc != 2) { Tcl_WrongNumArgs(interp, 1, objv, "names"); return TCL_ERROR; } resultPtr = Tcl_NewObj(); namedHashPtr = Tcl_FirstHashEntry(&fiPtr->namedTable, &search); while (namedHashPtr != NULL) { NamedFont *nfPtr = (NamedFont *)Tcl_GetHashValue(namedHashPtr); if (!nfPtr->deletePending) { char *string = (char *)Tcl_GetHashKey(&fiPtr->namedTable, namedHashPtr); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(string, TCL_INDEX_NONE)); } namedHashPtr = Tcl_NextHashEntry(&search); } Tcl_SetObjResult(interp, resultPtr); break; } } return TCL_OK; } /* *--------------------------------------------------------------------------- * * UpdateDependentFonts, TheWorldHasChanged, RecomputeWidgets -- * * Called when the attributes of a named font changes. Updates all the * instantiated fonts that depend on that named font and then uses the * brute force approach and prepares every widget to recompute its * geometry. * * Results: * None. * * Side effects: * Things get queued for redisplay. * *--------------------------------------------------------------------------- */ static void UpdateDependentFonts( TkFontInfo *fiPtr, /* Info about application's fonts. */ Tk_Window tkwin, /* A window in the application. */ Tcl_HashEntry *namedHashPtr)/* The named font that is changing. */ { Tcl_HashEntry *cacheHashPtr; Tcl_HashSearch search; TkFont *fontPtr; NamedFont *nfPtr = (NamedFont *)Tcl_GetHashValue(namedHashPtr); if (nfPtr->refCount == 0) { /* * Well nobody's using this named font, so don't have to tell any * widgets to recompute themselves. */ return; } cacheHashPtr = Tcl_FirstHashEntry(&fiPtr->fontCache, &search); while (cacheHashPtr != NULL) { for (fontPtr = (TkFont *)Tcl_GetHashValue(cacheHashPtr); fontPtr != NULL; fontPtr = fontPtr->nextPtr) { if (fontPtr->namedHashPtr == namedHashPtr) { TkpGetFontFromAttributes(fontPtr, tkwin, &nfPtr->fa); if (!fiPtr->updatePending) { fiPtr->updatePending = 1; Tcl_DoWhenIdle(TheWorldHasChanged, fiPtr); } } } cacheHashPtr = Tcl_NextHashEntry(&search); } } static void TheWorldHasChanged( void *clientData) /* Info about application's fonts. */ { TkFontInfo *fiPtr = (TkFontInfo *)clientData; /* * On macOS it is catastrophic to recompute all widgets while the * [NSView drawRect] method is drawing. The best that we can do in * that situation is to abort the recomputation and hope for the best. * This is ignored on other platforms. */ if (TkpWillDrawWidget(NULL)) { return; } fiPtr->updatePending = 0; RecomputeWidgets(fiPtr->mainPtr->winPtr); } static void RecomputeWidgets( TkWindow *winPtr) /* Window to which command is sent. */ { Tk_ClassWorldChangedProc *proc = Tk_GetClassProc(winPtr->classProcsPtr, worldChangedProc); TkWindow *tkwinPtr; if (proc != NULL) { proc(winPtr->instanceData); } /* * Notify all the descendants of this window that the world has changed. * * This could be done recursively or iteratively. The recursive version is * easier to implement and understand, and typically, windows with a -font * option will be leaf nodes in the widget hierarchy (buttons, labels, * etc.), so the recursion depth will be shallow. * * However, the additional overhead of the recursive calls may become a * performance problem if typical usage alters such that -font'ed widgets * appear high in the hierarchy, causing deep recursion. This could happen * with text widgets, or more likely with the labelframe * widget. With these widgets it is possible, even likely, that a * -font'ed widget (text or labelframe) will not be a leaf node, but * will instead have many descendants. If this is ever found to cause a * performance problem, it may be worth investigating an iterative version * of the code below. */ for (tkwinPtr=winPtr->childList ; tkwinPtr!=NULL ; tkwinPtr=tkwinPtr->nextPtr) { RecomputeWidgets(tkwinPtr); } /* * Broadcast font change virtually for mega-widget layout managers. * Do this after the font change has been propagated to core widgets. */ Tk_SendVirtualEvent((Tk_Window)winPtr, "TkWorldChanged", Tcl_NewStringObj("FontChanged",-1)); } /* *--------------------------------------------------------------------------- * * TkCreateNamedFont -- * * Create the specified named font with the given attributes in the named * font table associated with the interp. * * Results: * Returns TCL_OK if the font was successfully created, or TCL_ERROR if * the named font already existed. If TCL_ERROR is returned, an error * message is left in the interp's result. * * Side effects: * Assume there used to exist a named font by the specified name, and * that the named font had been deleted, but there were still some * widgets using the named font at the time it was deleted. If a new * named font is created with the same name, all those widgets that were * using the old named font will be redisplayed using the new named * font's attributes. * *--------------------------------------------------------------------------- */ int TkCreateNamedFont( Tcl_Interp *interp, /* Interp for error return (can be NULL). */ Tk_Window tkwin, /* A window associated with interp. */ const char *name, /* Name for the new named font. */ TkFontAttributes *faPtr) /* Attributes for the new named font. */ { TkFontInfo *fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr; Tcl_HashEntry *namedHashPtr; int isNew; NamedFont *nfPtr; namedHashPtr = Tcl_CreateHashEntry(&fiPtr->namedTable, name, &isNew); if (!isNew) { nfPtr = (NamedFont *)Tcl_GetHashValue(namedHashPtr); if (!nfPtr->deletePending) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "named font \"%s\" already exists", name)); Tcl_SetErrorCode(interp, "TK", "FONT", "EXISTS", NULL); } return TCL_ERROR; } /* * Recreating a named font with the same name as a previous named * font. Some widgets were still using that named font, so they need * to get redisplayed. */ nfPtr->fa = *faPtr; nfPtr->deletePending = 0; UpdateDependentFonts(fiPtr, tkwin, namedHashPtr); return TCL_OK; } nfPtr = (NamedFont *)ckalloc(sizeof(NamedFont)); nfPtr->deletePending = 0; Tcl_SetHashValue(namedHashPtr, nfPtr); nfPtr->fa = *faPtr; nfPtr->refCount = 0; nfPtr->deletePending = 0; return TCL_OK; } /* *--------------------------------------------------------------------------- * * TkDeleteNamedFont -- * * Delete the named font. If there are still widgets using this font, * then it isn't deleted right away. * *--------------------------------------------------------------------------- */ int TkDeleteNamedFont( Tcl_Interp *interp, /* Interp for error return (can be NULL). */ Tk_Window tkwin, /* A window associated with interp. */ const char *name) /* Name for the new named font. */ { TkFontInfo *fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr; NamedFont *nfPtr; Tcl_HashEntry *namedHashPtr; namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable, name); if (namedHashPtr == NULL) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "named font \"%s\" doesn't exist", name)); Tcl_SetErrorCode(interp, "TK", "LOOKUP", "FONT", name, NULL); } return TCL_ERROR; } nfPtr = (NamedFont *)Tcl_GetHashValue(namedHashPtr); if (nfPtr->refCount != 0) { nfPtr->deletePending = 1; } else { Tcl_DeleteHashEntry(namedHashPtr); ckfree(nfPtr); } return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tk_GetFont -- * * Given a string description of a font, map the description to a * corresponding Tk_Font that represents the font. * * Results: * The return value is token for the font, or NULL if an error prevented * the font from being created. If NULL is returned, an error message * will be left in the interp's result. * * Side effects: * The font is added to an internal database with a reference count. For * each call to this function, there should eventually be a call to * Tk_FreeFont() or Tk_FreeFontFromObj() so that the database is cleaned * up when fonts aren't in use anymore. * *--------------------------------------------------------------------------- */ Tk_Font Tk_GetFont( Tcl_Interp *interp, /* Interp for database and error return. */ Tk_Window tkwin, /* For display on which font will be used. */ const char *string) /* String describing font, as: named font, * native format, or parseable string. */ { Tk_Font tkfont; Tcl_Obj *strPtr; strPtr = Tcl_NewStringObj(string, TCL_INDEX_NONE); Tcl_IncrRefCount(strPtr); tkfont = Tk_AllocFontFromObj(interp, tkwin, strPtr); Tcl_DecrRefCount(strPtr); return tkfont; } /* *--------------------------------------------------------------------------- * * Tk_AllocFontFromObj -- * * Given a string description of a font, map the description to a * corresponding Tk_Font that represents the font. * * Results: * The return value is token for the font, or NULL if an error prevented * the font from being created. If NULL is returned, an error message * will be left in interp's result object. * * Side effects: * The font is added to an internal database with a reference count. For * each call to this function, there should eventually be a call to * Tk_FreeFont() or Tk_FreeFontFromObj() so that the database is cleaned * up when fonts aren't in use anymore. * *--------------------------------------------------------------------------- */ Tk_Font Tk_AllocFontFromObj( Tcl_Interp *interp, /* Interp for database and error return. */ Tk_Window tkwin, /* For screen on which font will be used. */ Tcl_Obj *objPtr) /* Object describing font, as: named font, * native format, or parseable string. */ { TkFontInfo *fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr; Tcl_HashEntry *cacheHashPtr, *namedHashPtr; TkFont *fontPtr, *firstFontPtr, *oldFontPtr; int isNew, descent; NamedFont *nfPtr; if (objPtr->typePtr != &tkFontObjType.objType || objPtr->internalRep.twoPtrValue.ptr2 != fiPtr) { SetFontFromAny(interp, objPtr); } oldFontPtr = (TkFont *)objPtr->internalRep.twoPtrValue.ptr1; if (oldFontPtr != NULL) { if (oldFontPtr->resourceRefCount == 0) { /* * This is a stale reference: it refers to a TkFont that's no * longer in use. Clear the reference. */ FreeFontObj(objPtr); oldFontPtr = NULL; } else if (Tk_Screen(tkwin) == oldFontPtr->screen) { oldFontPtr->resourceRefCount++; return (Tk_Font) oldFontPtr; } } /* * Next, search the list of fonts that have the name we want, to see if * one of them is for the right screen. */ isNew = 0; if (oldFontPtr != NULL) { cacheHashPtr = oldFontPtr->cacheHashPtr; FreeFontObj(objPtr); } else { cacheHashPtr = Tcl_CreateHashEntry(&fiPtr->fontCache, Tcl_GetString(objPtr), &isNew); } firstFontPtr = (TkFont *)Tcl_GetHashValue(cacheHashPtr); for (fontPtr = firstFontPtr; (fontPtr != NULL); fontPtr = fontPtr->nextPtr) { if (Tk_Screen(tkwin) == fontPtr->screen) { fontPtr->resourceRefCount++; fontPtr->objRefCount++; objPtr->internalRep.twoPtrValue.ptr1 = fontPtr; objPtr->internalRep.twoPtrValue.ptr2 = fiPtr; return (Tk_Font) fontPtr; } } /* * The desired font isn't in the table. Make a new one. */ namedHashPtr = Tcl_FindHashEntry(&fiPtr->namedTable, Tcl_GetString(objPtr)); if (namedHashPtr != NULL) { /* * Construct a font based on a named font. */ nfPtr = (NamedFont *)Tcl_GetHashValue(namedHashPtr); nfPtr->refCount++; fontPtr = TkpGetFontFromAttributes(NULL, tkwin, &nfPtr->fa); } else { /* * Native font? */ fontPtr = TkpGetNativeFont(tkwin, Tcl_GetString(objPtr)); if (fontPtr == NULL) { TkFontAttributes fa; Tcl_Obj *dupObjPtr = Tcl_DuplicateObj(objPtr); if (ParseFontNameObj(interp, tkwin, dupObjPtr, &fa) != TCL_OK) { if (isNew) { Tcl_DeleteHashEntry(cacheHashPtr); } Tcl_DecrRefCount(dupObjPtr); return NULL; } Tcl_DecrRefCount(dupObjPtr); /* * String contained the attributes inline. */ fontPtr = TkpGetFontFromAttributes(NULL, tkwin, &fa); } } /* * Detect the system font engine going wrong and fail more gracefully. */ if (fontPtr == NULL) { if (isNew) { Tcl_DeleteHashEntry(cacheHashPtr); } Tcl_SetObjResult(interp, Tcl_NewStringObj( "failed to allocate font due to internal system font engine" " problem", TCL_INDEX_NONE)); Tcl_SetErrorCode(interp, "TK", "FONT", "INTERNAL_PROBLEM", NULL); return NULL; } fontPtr->resourceRefCount = 1; fontPtr->objRefCount = 1; fontPtr->cacheHashPtr = cacheHashPtr; fontPtr->namedHashPtr = namedHashPtr; fontPtr->screen = Tk_Screen(tkwin); fontPtr->nextPtr = firstFontPtr; Tcl_SetHashValue(cacheHashPtr, fontPtr); Tk_MeasureChars((Tk_Font) fontPtr, "0", 1, -1, 0, &fontPtr->tabWidth); if (fontPtr->tabWidth == 0) { fontPtr->tabWidth = fontPtr->fm.maxWidth; } fontPtr->tabWidth *= 8; /* * Make sure the tab width isn't zero (some fonts may not have enough * information to set a reasonable tab width). */ if (fontPtr->tabWidth == 0) { fontPtr->tabWidth = 1; } /* * Get information used for drawing underlines in generic code on a * non-underlined font. */ descent = fontPtr->fm.descent; fontPtr->underlinePos = descent / 2; fontPtr->underlineHeight = (int) (TkFontGetPixels(tkwin, fontPtr->fa.size) / 10 + 0.5); if (fontPtr->underlineHeight == 0) { fontPtr->underlineHeight = 1; } if (fontPtr->underlinePos + fontPtr->underlineHeight > descent) { /* * If this set of values would cause the bottom of the underline bar * to stick below the descent of the font, jack the underline up a bit * higher. */ fontPtr->underlineHeight = descent - fontPtr->underlinePos; if (fontPtr->underlineHeight == 0) { fontPtr->underlinePos--; fontPtr->underlineHeight = 1; } } objPtr->internalRep.twoPtrValue.ptr1 = fontPtr; objPtr->internalRep.twoPtrValue.ptr2 = fiPtr; return (Tk_Font) fontPtr; } /* *---------------------------------------------------------------------- * * Tk_GetFontFromObj -- * * Find the font that corresponds to a given object. The font must have * already been created by Tk_GetFont or Tk_AllocFontFromObj. * * Results: * The return value is a token for the font that matches objPtr and is * suitable for use in tkwin. * * Side effects: * If the object is not already a font ref, the conversion will free any * old internal representation. * *---------------------------------------------------------------------- */ Tk_Font Tk_GetFontFromObj( Tk_Window tkwin, /* The window that the font will be used * in. */ Tcl_Obj *objPtr) /* The object from which to get the font. */ { TkFontInfo *fiPtr = ((TkWindow *) tkwin)->mainPtr->fontInfoPtr; TkFont *fontPtr; Tcl_HashEntry *hashPtr; if (objPtr->typePtr != &tkFontObjType.objType || objPtr->internalRep.twoPtrValue.ptr2 != fiPtr) { SetFontFromAny(NULL, objPtr); } fontPtr = (TkFont *)objPtr->internalRep.twoPtrValue.ptr1; if (fontPtr != NULL) { if (fontPtr->resourceRefCount == 0) { /* * This is a stale reference: it refers to a TkFont that's no * longer in use. Clear the reference. */ FreeFontObj(objPtr); fontPtr = NULL; } else if (Tk_Screen(tkwin) == fontPtr->screen) { fontPtr->resourceRefCount++; return (Tk_Font) fontPtr; } } /* * Next, search the list of fonts that have the name we want, to see if * one of them is for the right screen. */ if (fontPtr != NULL) { hashPtr = fontPtr->cacheHashPtr; FreeFontObj(objPtr); } else { hashPtr = Tcl_FindHashEntry(&fiPtr->fontCache, Tcl_GetString(objPtr)); } if (hashPtr != NULL) { for (fontPtr = (TkFont *)Tcl_GetHashValue(hashPtr); fontPtr != NULL; fontPtr = fontPtr->nextPtr) { if (Tk_Screen(tkwin) == fontPtr->screen) { fontPtr->objRefCount++; objPtr->internalRep.twoPtrValue.ptr1 = fontPtr; objPtr->internalRep.twoPtrValue.ptr2 = fiPtr; return (Tk_Font) fontPtr; } } } Tcl_Panic("Tk_GetFontFromObj called with non-existent font!"); return NULL; } /* *---------------------------------------------------------------------- * * SetFontFromAny -- * * Convert the internal representation of a Tcl object to the font * internal form. * * Results: * Always returns TCL_OK. * * Side effects: * The object is left with its typePtr pointing to tkFontObjType. The * TkFont pointer is NULL. * *---------------------------------------------------------------------- */ static int SetFontFromAny( TCL_UNUSED(Tcl_Interp *), /* Used for error reporting if not NULL. */ Tcl_Obj *objPtr) /* The object to convert. */ { const Tcl_ObjType *typePtr; /* * Free the old internalRep before setting the new one. */ Tcl_GetString(objPtr); typePtr = objPtr->typePtr; if ((typePtr != NULL) && (typePtr->freeIntRepProc != NULL)) { typePtr->freeIntRepProc(objPtr); } objPtr->typePtr = &tkFontObjType.objType; objPtr->internalRep.twoPtrValue.ptr1 = NULL; objPtr->internalRep.twoPtrValue.ptr2 = NULL; return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tk_NameOfFont -- * * Given a font, return a textual string identifying it. * * Results: * The return value is the description that was passed to Tk_GetFont() to * create the font. The storage for the returned string is only * guaranteed to persist until the font is deleted. The caller should not * modify this string. * * Side effects: * None. * *--------------------------------------------------------------------------- */ const char * Tk_NameOfFont( Tk_Font tkfont) /* Font whose name is desired. */ { TkFont *fontPtr = (TkFont *) tkfont; return fontPtr->cacheHashPtr->key.string; } /* *--------------------------------------------------------------------------- * * Tk_FreeFont -- * * Called to release a font allocated by Tk_GetFont(). * * Results: * None. * * Side effects: * The reference count associated with font is decremented, and only * deallocated when no one is using it. * *--------------------------------------------------------------------------- */ void Tk_FreeFont( Tk_Font tkfont) /* Font to be released. */ { TkFont *fontPtr = (TkFont *) tkfont, *prevPtr; NamedFont *nfPtr; if (fontPtr == NULL) { return; } if (fontPtr->resourceRefCount-- > 1) { return; } if (fontPtr->namedHashPtr != NULL) { /* * This font derived from a named font. Reduce the reference count on * the named font and free it if no-one else is using it. */ nfPtr = (NamedFont *)Tcl_GetHashValue(fontPtr->namedHashPtr); if ((nfPtr->refCount-- <= 1) && nfPtr->deletePending) { Tcl_DeleteHashEntry(fontPtr->namedHashPtr); ckfree(nfPtr); } } prevPtr = (TkFont *)Tcl_GetHashValue(fontPtr->cacheHashPtr); if (prevPtr == fontPtr) { if (fontPtr->nextPtr == NULL) { Tcl_DeleteHashEntry(fontPtr->cacheHashPtr); } else { Tcl_SetHashValue(fontPtr->cacheHashPtr, fontPtr->nextPtr); } } else { while (prevPtr->nextPtr != fontPtr) { prevPtr = prevPtr->nextPtr; } prevPtr->nextPtr = fontPtr->nextPtr; } TkpDeleteFont(fontPtr); if (fontPtr->objRefCount == 0) { ckfree(fontPtr); } } /* *--------------------------------------------------------------------------- * * Tk_FreeFontFromObj -- * * Called to release a font inside a Tcl_Obj *. Decrements the refCount * of the font and removes it from the hash tables if necessary. * * Results: * None. * * Side effects: * The reference count associated with font is decremented, and only * deallocated when no one is using it. * *--------------------------------------------------------------------------- */ void Tk_FreeFontFromObj( Tk_Window tkwin, /* The window this font lives in. Needed for * the screen value. */ Tcl_Obj *objPtr) /* The Tcl_Obj * to be freed. */ { Tk_FreeFont(Tk_GetFontFromObj(tkwin, objPtr)); } /* *--------------------------------------------------------------------------- * * FreeFontObjProc, FreeFontObj -- * * This proc is called to release an object reference to a font. Called * when the object's internal rep is released or when the cached fontPtr * needs to be changed. * * Results: * None. * * Side effects: * The object reference count is decremented. When both it and the hash * ref count go to zero, the font's resources are released. * *--------------------------------------------------------------------------- */ static void FreeFontObjProc( Tcl_Obj *objPtr) /* The object we are releasing. */ { FreeFontObj(objPtr); objPtr->typePtr = NULL; } static void FreeFontObj( Tcl_Obj *objPtr) /* The object we are releasing. */ { TkFont *fontPtr = (TkFont *)objPtr->internalRep.twoPtrValue.ptr1; if (fontPtr != NULL) { if ((fontPtr->objRefCount-- <= 1) && (fontPtr->resourceRefCount == 0)) { ckfree(fontPtr); } objPtr->internalRep.twoPtrValue.ptr1 = NULL; objPtr->internalRep.twoPtrValue.ptr2 = NULL; } } /* *--------------------------------------------------------------------------- * * DupFontObjProc -- * * When a cached font object is duplicated, this is called to update the * internal reps. * * Results: * None. * * Side effects: * The font's objRefCount is incremented and the internal rep of the copy * is set to point to it. * *--------------------------------------------------------------------------- */ static void DupFontObjProc( Tcl_Obj *srcObjPtr, /* The object we are copying from. */ Tcl_Obj *dupObjPtr) /* The object we are copying to. */ { TkFont *fontPtr = (TkFont *)srcObjPtr->internalRep.twoPtrValue.ptr1; dupObjPtr->typePtr = srcObjPtr->typePtr; dupObjPtr->internalRep.twoPtrValue.ptr1 = fontPtr; dupObjPtr->internalRep.twoPtrValue.ptr2 = srcObjPtr->internalRep.twoPtrValue.ptr2; if (fontPtr != NULL) { fontPtr->objRefCount++; } } /* *--------------------------------------------------------------------------- * * Tk_FontId -- * * Given a font, return an opaque handle that should be selected into the * XGCValues structure in order to get the constructed gc to use this * font. This function would go away if the XGCValues structure were * replaced with a TkGCValues structure. * * Results: * As above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Font Tk_FontId( Tk_Font tkfont) /* Font that is going to be selected into * GC. */ { TkFont *fontPtr = (TkFont *) tkfont; return fontPtr->fid; } /* *--------------------------------------------------------------------------- * * Tk_GetFontMetrics -- * * Returns overall ascent and descent metrics for the given font. These * values can be used to space multiple lines of text and to align the * baselines of text in different fonts. * * Results: * If *heightPtr is non-NULL, it is filled with the overall height of the * font, which is the sum of the ascent and descent. If *ascentPtr or * *descentPtr is non-NULL, they are filled with the ascent and/or * descent information for the font. * * Side effects: * None. * *--------------------------------------------------------------------------- */ void Tk_GetFontMetrics( Tk_Font tkfont, /* Font in which metrics are calculated. */ Tk_FontMetrics *fmPtr) /* Pointer to structure in which font metrics * for tkfont will be stored. */ { TkFont *fontPtr = (TkFont *) tkfont; fmPtr->ascent = fontPtr->fm.ascent; fmPtr->descent = fontPtr->fm.descent; fmPtr->linespace = fontPtr->fm.ascent + fontPtr->fm.descent; } /* *--------------------------------------------------------------------------- * * Tk_PostscriptFontName -- * * Given a Tk_Font, return the name of the corresponding Postscript font. * * Results: * The return value is the pointsize of the given Tk_Font. The name of * the Postscript font is appended to dsPtr. * * Side effects: * If the font does not exist on the printer, the print job will fail at * print time. Given a "reasonable" Postscript printer, the following * Tk_Font font families should print correctly: * * Avant Garde, Arial, Bookman, Courier, Courier New, Geneva, * Helvetica, Monaco, New Century Schoolbook, New York, * Palatino, Symbol, Times, Times New Roman, Zapf Chancery, * and Zapf Dingbats. * * Any other Tk_Font font families may not print correctly because the * computed Postscript font name may be incorrect. * *--------------------------------------------------------------------------- */ int Tk_PostscriptFontName( Tk_Font tkfont, /* Font in which text will be printed. */ Tcl_DString *dsPtr) /* Pointer to an initialized Tcl_DString to * which the name of the Postscript font that * corresponds to tkfont will be appended. */ { TkFont *fontPtr = (TkFont *) tkfont; Tk_Uid family, weightString, slantString; char *src, *dest; int upper, len; len = Tcl_DStringLength(dsPtr); /* * Convert the case-insensitive Tk_Font family name to the case-sensitive * Postscript family name. Take out any spaces and capitalize the first * letter of each word. */ family = fontPtr->fa.family; if (strncasecmp(family, "itc ", 4) == 0) { family = family + 4; } if ((strcasecmp(family, "Arial") == 0) || (strcasecmp(family, "Geneva") == 0)) { family = "Helvetica"; } else if ((strcasecmp(family, "Times New Roman") == 0) || (strcasecmp(family, "New York") == 0)) { family = "Times"; } else if ((strcasecmp(family, "Courier New") == 0) || (strcasecmp(family, "Monaco") == 0)) { family = "Courier"; } else if (strcasecmp(family, "AvantGarde") == 0) { family = "AvantGarde"; } else if (strcasecmp(family, "ZapfChancery") == 0) { family = "ZapfChancery"; } else if (strcasecmp(family, "ZapfDingbats") == 0) { family = "ZapfDingbats"; } else { int ch; /* * Inline, capitalize the first letter of each word, lowercase the * rest of the letters in each word, and then take out the spaces * between the words. This may make the DString shorter, which is safe * to do. */ Tcl_DStringAppend(dsPtr, family, TCL_INDEX_NONE); src = dest = Tcl_DStringValue(dsPtr) + len; upper = 1; for (; *src != '\0'; ) { while (isspace(UCHAR(*src))) { /* INTL: ISO space */ src++; upper = 1; } src += Tcl_UtfToUniChar(src, &ch); if (upper) { ch = Tcl_UniCharToUpper(ch); upper = 0; } else { ch = Tcl_UniCharToLower(ch); } dest += Tcl_UniCharToUtf(ch, dest); } *dest = '\0'; Tcl_DStringSetLength(dsPtr, dest - Tcl_DStringValue(dsPtr)); family = Tcl_DStringValue(dsPtr) + len; } if (family != Tcl_DStringValue(dsPtr) + len) { Tcl_DStringAppend(dsPtr, family, TCL_INDEX_NONE); family = Tcl_DStringValue(dsPtr) + len; } if (strcasecmp(family, "NewCenturySchoolbook") == 0) { Tcl_DStringSetLength(dsPtr, len); Tcl_DStringAppend(dsPtr, "NewCenturySchlbk", TCL_INDEX_NONE); family = Tcl_DStringValue(dsPtr) + len; } /* * Get the string to use for the weight. */ weightString = NULL; if (fontPtr->fa.weight == TK_FW_NORMAL) { if (strcmp(family, "Bookman") == 0) { weightString = "Light"; } else if (strcmp(family, "AvantGarde") == 0) { weightString = "Book"; } else if (strcmp(family, "ZapfChancery") == 0) { weightString = "Medium"; } } else { if ((strcmp(family, "Bookman") == 0) || (strcmp(family, "AvantGarde") == 0)) { weightString = "Demi"; } else { weightString = "Bold"; } } /* * Get the string to use for the slant. */ slantString = NULL; if (fontPtr->fa.slant == TK_FS_ROMAN) { /* Do nothing */ } else if ((strcmp(family, "Helvetica") == 0) || (strcmp(family, "Courier") == 0) || (strcmp(family, "AvantGarde") == 0)) { slantString = "Oblique"; } else { slantString = "Italic"; } /* * The string "Roman" needs to be added to some fonts that are not bold * and not italic. */ if ((slantString == NULL) && (weightString == NULL)) { if ((strcmp(family, "Times") == 0) || (strcmp(family, "NewCenturySchlbk") == 0) || (strcmp(family, "Palatino") == 0)) { Tcl_DStringAppend(dsPtr, "-Roman", TCL_INDEX_NONE); } } else { Tcl_DStringAppend(dsPtr, "-", TCL_INDEX_NONE); if (weightString != NULL) { Tcl_DStringAppend(dsPtr, weightString, TCL_INDEX_NONE); } if (slantString != NULL) { Tcl_DStringAppend(dsPtr, slantString, TCL_INDEX_NONE); } } return (int)(fontPtr->fa.size + 0.5); } /* *--------------------------------------------------------------------------- * * Tk_TextWidth -- * * A wrapper function for the more complicated interface of * Tk_MeasureChars. Computes how much space the given simple string * needs. * * Results: * The return value is the width (in pixels) of the given string. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tk_TextWidth( Tk_Font tkfont, /* Font in which text will be measured. */ const char *string, /* String whose width will be computed. */ Tcl_Size numBytes) /* Number of bytes to consider from string, or * TCL_INDEX_NONE for strlen(). */ { int width; if (numBytes < 0) { numBytes = strlen(string); } Tk_MeasureChars(tkfont, string, numBytes, -1, 0, &width); return width; } /* *--------------------------------------------------------------------------- * * Tk_UnderlineChars, TkUnderlineCharsInContext -- * * These procedures draw an underline for a given range of characters in * a given string. They don't draw the characters (which are assumed to * have been displayed previously); they just draw the underline. These * procedures would mainly be used to quickly underline a few characters * without having to construct an underlined font. To produce properly * underlined text, the appropriate underlined font should be constructed * and used. * * Results: * None. * * Side effects: * Information gets displayed in "drawable". * *---------------------------------------------------------------------- */ void Tk_UnderlineChars( Display *display, /* Display on which to draw. */ Drawable drawable, /* Window or pixmap in which to draw. */ GC gc, /* Graphics context for actually drawing * line. */ Tk_Font tkfont, /* Font used in GC; must have been allocated * by Tk_GetFont(). Used for character * dimensions, etc. */ const char *string, /* String containing characters to be * underlined or overstruck. */ int x, int y, /* Coordinates at which first character of * string is drawn. */ Tcl_Size firstByte, /* Index of first byte of first character. */ Tcl_Size lastByte) /* Index of first byte after the last * character. */ { TkUnderlineCharsInContext(display, drawable, gc, tkfont, string, lastByte, x, y, firstByte, lastByte); } void TkUnderlineCharsInContext( Display *display, /* Display on which to draw. */ Drawable drawable, /* Window or pixmap in which to draw. */ GC gc, /* Graphics context for actually drawing * line. */ Tk_Font tkfont, /* Font used in GC; must have been allocated * by Tk_GetFont(). Used for character * dimensions, etc. */ const char *string, /* String containing characters to be * underlined or overstruck. */ Tcl_Size numBytes, /* Number of bytes in string. */ int x, int y, /* Coordinates at which the first character of * the whole string would be drawn. */ Tcl_Size firstByte, /* Index of first byte of first character. */ Tcl_Size lastByte) /* Index of first byte after the last * character. */ { TkFont *fontPtr = (TkFont *) tkfont; int startX, endX; TkpMeasureCharsInContext(tkfont, string, numBytes, 0, firstByte, -1, 0, &startX); TkpMeasureCharsInContext(tkfont, string, numBytes, 0, lastByte, -1, 0, &endX); XFillRectangle(display, drawable, gc, x + startX, y + fontPtr->underlinePos, (unsigned) (endX - startX), (unsigned) fontPtr->underlineHeight); } /* *--------------------------------------------------------------------------- * * Tk_ComputeTextLayout -- * * Computes the amount of screen space needed to display a multi-line, * justified string of text. Records all the measurements that were done * to determine to size and positioning of the individual lines of text; * this information can be used by the Tk_DrawTextLayout() function to * display the text quickly (without remeasuring it). * * This function is useful for simple widgets that want to display * single-font, multi-line text and want Tk to handle the details. * * Results: * The return value is a Tk_TextLayout token that holds the measurement * information for the given string. The token is only valid for the * given string. If the string is freed, the token is no longer valid and * must also be freed. To free the token, call Tk_FreeTextLayout(). * * The dimensions of the screen area needed to display the text are * stored in *widthPtr and *heightPtr. * * Side effects: * Memory is allocated to hold the measurement information. * *--------------------------------------------------------------------------- */ Tk_TextLayout Tk_ComputeTextLayout( Tk_Font tkfont, /* Font that will be used to display text. */ const char *string, /* String whose dimensions are to be * computed. */ Tcl_Size numChars, /* Number of characters to consider from * string, or TCL_INDEX_NONE for strlen(). */ int wrapLength, /* Longest permissible line length, in pixels. * <= 0 means no automatic wrapping: just let * lines get as long as needed. */ Tk_Justify justify, /* How to justify lines. */ int flags, /* Flag bits OR-ed together. TK_IGNORE_TABS * means that tab characters should not be * expanded. TK_IGNORE_NEWLINES means that * newline characters should not cause a line * break. */ int *widthPtr, /* Filled with width of string. */ int *heightPtr) /* Filled with height of string. */ { TkFont *fontPtr = (TkFont *) tkfont; const char *start, *endp, *special; Tcl_Size n; int y, bytesThisChunk, maxChunks, curLine, layoutHeight; int baseline, height, curX, newX, maxWidth, *lineLengths; TextLayout *layoutPtr; LayoutChunk *chunkPtr; const TkFontMetrics *fmPtr; Tcl_DString lineBuffer; Tcl_DStringInit(&lineBuffer); if ((fontPtr == NULL) || (string == NULL)) { if (widthPtr != NULL) { *widthPtr = 0; } if (heightPtr != NULL) { *heightPtr = 0; } return NULL; } fmPtr = &fontPtr->fm; height = fmPtr->ascent + fmPtr->descent; if (numChars < 0) { numChars = Tcl_NumUtfChars(string, TCL_INDEX_NONE); } if (wrapLength == 0) { wrapLength = -1; } maxChunks = 1; layoutPtr = (TextLayout *)ckalloc(offsetof(TextLayout, chunks) + maxChunks * sizeof(LayoutChunk)); layoutPtr->tkfont = tkfont; layoutPtr->string = string; layoutPtr->numChunks = 0; baseline = fmPtr->ascent; maxWidth = 0; /* * Divide the string up into simple strings and measure each string. */ curX = 0; endp = Tcl_UtfAtIndex(string, numChars); special = string; flags &= TK_IGNORE_TABS | TK_IGNORE_NEWLINES; flags |= TK_WHOLE_WORDS | TK_AT_LEAST_ONE; for (start = string; start < endp; ) { if (start >= special) { /* * Find the next special character in the string. * * INTL: Note that it is safe to increment by byte, because we are * looking for 7-bit characters that will appear unchanged in * UTF-8. At some point we may need to support the full Unicode * whitespace set. */ for (special = start; special < endp; special++) { if (!(flags & TK_IGNORE_NEWLINES)) { if ((*special == '\n') || (*special == '\r')) { break; } } if (!(flags & TK_IGNORE_TABS)) { if (*special == '\t') { break; } } } } /* * Special points at the next special character (or the end of the * string). Process characters between start and special. */ chunkPtr = NULL; if (start < special) { bytesThisChunk = Tk_MeasureChars(tkfont, start, special - start, wrapLength - curX, flags, &newX); newX += curX; flags &= ~TK_AT_LEAST_ONE; if (bytesThisChunk > 0) { chunkPtr = NewChunk(&layoutPtr, &maxChunks, start, bytesThisChunk, curX, newX, baseline); start += bytesThisChunk; curX = newX; } } if ((start == special) && (special < endp)) { /* * Handle the special character. * * INTL: Special will be pointing at a 7-bit character so we can * safely treat it as a single byte. */ chunkPtr = NULL; if (*special == '\t') { newX = curX + fontPtr->tabWidth; newX -= newX % fontPtr->tabWidth; NewChunk(&layoutPtr, &maxChunks, start, 1, curX, newX, baseline)->numDisplayChars = -1; start++; curX = newX; flags &= ~TK_AT_LEAST_ONE; if ((start < endp) && ((wrapLength <= 0) || (newX <= wrapLength))) { /* * More chars can still fit on this line. */ continue; } } else { NewChunk(&layoutPtr, &maxChunks, start, 1, curX, curX, baseline)->numDisplayChars = -1; start++; goto wrapLine; } } /* * No more characters are going to go on this line, either because no * more characters can fit or there are no more characters left. * Consume all extra spaces at end of line. */ while ((start < endp) && isspace(UCHAR(*start))) { /* INTL: ISO space */ if (!(flags & TK_IGNORE_NEWLINES)) { if ((*start == '\n') || (*start == '\r')) { break; } } if (!(flags & TK_IGNORE_TABS)) { if (*start == '\t') { break; } } start++; } if (chunkPtr != NULL) { const char *end; /* * Append all the extra spaces on this line to the end of the last * text chunk. This is a little tricky because we are switching * back and forth between characters and bytes. */ end = chunkPtr->start + chunkPtr->numBytes; bytesThisChunk = start - end; if (bytesThisChunk > 0) { bytesThisChunk = Tk_MeasureChars(tkfont, end, bytesThisChunk, -1, 0, &chunkPtr->totalWidth); chunkPtr->numBytes += bytesThisChunk; chunkPtr->numChars += Tcl_NumUtfChars(end, bytesThisChunk); chunkPtr->totalWidth += curX; } } wrapLine: flags |= TK_AT_LEAST_ONE; /* * Save current line length, then move current position to start of * next line. */ if (curX > maxWidth) { maxWidth = curX; } /* * Remember width of this line, so that all chunks on this line can be * centered or right justified, if necessary. */ Tcl_DStringAppend(&lineBuffer, (char *) &curX, sizeof(curX)); curX = 0; baseline += height; } /* * If last line ends with a newline, then we need to make a 0 width chunk * on the next line. Otherwise "Hello" and "Hello\n" are the same height. */ if ((layoutPtr->numChunks > 0) && !(flags & TK_IGNORE_NEWLINES)) { if (layoutPtr->chunks[layoutPtr->numChunks - 1].start[0] == '\n') { chunkPtr = NewChunk(&layoutPtr, &maxChunks, start, 0, curX, curX, baseline); chunkPtr->numDisplayChars = -1; Tcl_DStringAppend(&lineBuffer, (char *) &curX, sizeof(curX)); baseline += height; } } layoutPtr->width = maxWidth; layoutHeight = baseline - fmPtr->ascent; if (layoutPtr->numChunks == 0) { layoutHeight = height; /* * This fake chunk is used by the other functions so that they can * pretend that there is a chunk with no chars in it, which makes the * coding simpler. */ layoutPtr->numChunks = 1; layoutPtr->chunks[0].start = string; layoutPtr->chunks[0].numBytes = 0; layoutPtr->chunks[0].numChars = 0; layoutPtr->chunks[0].numDisplayChars = -1; layoutPtr->chunks[0].x = 0; layoutPtr->chunks[0].y = fmPtr->ascent; layoutPtr->chunks[0].totalWidth = 0; layoutPtr->chunks[0].displayWidth = 0; } else { /* * Using maximum line length, shift all the chunks so that the lines * are all justified correctly. */ curLine = 0; chunkPtr = layoutPtr->chunks; y = chunkPtr->y; lineLengths = (int *) Tcl_DStringValue(&lineBuffer); for (n = 0; n < layoutPtr->numChunks; n++) { int extra; if (chunkPtr->y != y) { curLine++; y = chunkPtr->y; } extra = maxWidth - lineLengths[curLine]; if (justify == TK_JUSTIFY_CENTER) { chunkPtr->x += extra / 2; } else if (justify == TK_JUSTIFY_RIGHT) { chunkPtr->x += extra; } chunkPtr++; } } if (widthPtr != NULL) { *widthPtr = layoutPtr->width; } if (heightPtr != NULL) { *heightPtr = layoutHeight; } Tcl_DStringFree(&lineBuffer); return (Tk_TextLayout) layoutPtr; } /* *--------------------------------------------------------------------------- * * Tk_FreeTextLayout -- * * This function is called to release the storage associated with a * Tk_TextLayout when it is no longer needed. * * Results: * None. * * Side effects: * Memory is freed. * *--------------------------------------------------------------------------- */ void Tk_FreeTextLayout( Tk_TextLayout textLayout) /* The text layout to be released. */ { TextLayout *layoutPtr = (TextLayout *) textLayout; if (layoutPtr != NULL) { ckfree(layoutPtr); } } /* *--------------------------------------------------------------------------- * * Tk_DrawTextLayout -- * * Use the information in the Tk_TextLayout token to display a * multi-line, justified string of text. * * This function is useful for simple widgets that need to display * single-font, multi-line text and want Tk to handle the details. * * Results: * None. * * Side effects: * Text drawn on the screen. * *--------------------------------------------------------------------------- */ void Tk_DrawTextLayout( Display *display, /* Display on which to draw. */ Drawable drawable, /* Window or pixmap in which to draw. */ GC gc, /* Graphics context to use for drawing * text. */ Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ int x, int y, /* Upper-left hand corner of rectangle in * which to draw (pixels). */ Tcl_Size firstChar, /* The index of the first character to draw * from the given text item. 0 specifies the * beginning. */ Tcl_Size lastChar) /* The index just after the last character to * draw from the given text item. A number < 0 * means to draw all characters. */ { #if 0 /* Use TkDrawAngledTextLayout() implementation - testing purposes at this point */ TkDrawAngledTextLayout(display, drawable, gc, layout, x, y, 0.0, firstChar, lastChar); #else TextLayout *layoutPtr = (TextLayout *) layout; int i, drawX; Tcl_Size numDisplayChars; const char *firstByte, *lastByte; LayoutChunk *chunkPtr; if (layoutPtr == NULL) { return; } if (lastChar < 0) { lastChar = 100000000; } chunkPtr = layoutPtr->chunks; for (i = 0; i < layoutPtr->numChunks; i++) { numDisplayChars = chunkPtr->numDisplayChars; if ((numDisplayChars > 0) && (firstChar < numDisplayChars)) { if (firstChar <= 0) { drawX = 0; firstChar = 0; firstByte = chunkPtr->start; } else { firstByte = Tcl_UtfAtIndex(chunkPtr->start, firstChar); Tk_MeasureChars(layoutPtr->tkfont, chunkPtr->start, firstByte - chunkPtr->start, -1, 0, &drawX); } if (lastChar < numDisplayChars) { numDisplayChars = lastChar; } lastByte = Tcl_UtfAtIndex(chunkPtr->start, numDisplayChars); #ifdef TK_DRAW_IN_CONTEXT TkpDrawCharsInContext(display, drawable, gc, layoutPtr->tkfont, chunkPtr->start, chunkPtr->numBytes, firstByte - chunkPtr->start, lastByte - firstByte, x+chunkPtr->x, y+chunkPtr->y); #else /* !TK_DRAW_IN_CONTEXT */ Tk_DrawChars(display, drawable, gc, layoutPtr->tkfont, firstByte, lastByte - firstByte, x+chunkPtr->x+drawX, y+chunkPtr->y); #endif /* TK_DRAW_IN_CONTEXT */ } firstChar -= chunkPtr->numChars; lastChar -= chunkPtr->numChars; if (lastChar <= 0) { break; } chunkPtr++; } #endif /* Use TkDrawAngledTextLayout() implementation */ } void TkDrawAngledTextLayout( Display *display, /* Display on which to draw. */ Drawable drawable, /* Window or pixmap in which to draw. */ GC gc, /* Graphics context to use for drawing * text. */ Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ int x, int y, /* Upper-left hand corner of rectangle in * which to draw (pixels). */ double angle, Tcl_Size firstChar, /* The index of the first character to draw * from the given text item. 0 specifies the * beginning. */ Tcl_Size lastChar) /* The index just after the last character to * draw from the given text item. A number < 0 * means to draw all characters. */ { TextLayout *layoutPtr = (TextLayout *) layout; int i, numDisplayChars, drawX; const char *firstByte, *lastByte; LayoutChunk *chunkPtr; double sinA = sin(angle * PI/180.0), cosA = cos(angle * PI/180.0); if (layoutPtr == NULL) { return; } if (lastChar < 0) { lastChar = 100000000; } chunkPtr = layoutPtr->chunks; for (i = 0; i < layoutPtr->numChunks; i++) { numDisplayChars = chunkPtr->numDisplayChars; if ((numDisplayChars > 0) && (firstChar < numDisplayChars)) { double dx, dy; if (firstChar <= 0) { drawX = 0; firstChar = 0; firstByte = chunkPtr->start; } else { firstByte = Tcl_UtfAtIndex(chunkPtr->start, firstChar); Tk_MeasureChars(layoutPtr->tkfont, chunkPtr->start, firstByte - chunkPtr->start, -1, 0, &drawX); } if (lastChar < numDisplayChars) { numDisplayChars = lastChar; } lastByte = Tcl_UtfAtIndex(chunkPtr->start, numDisplayChars); #ifdef TK_DRAW_IN_CONTEXT dx = cosA * (chunkPtr->x) + sinA * (chunkPtr->y); dy = -sinA * (chunkPtr->x) + cosA * (chunkPtr->y); if (angle == 0.0) { TkpDrawCharsInContext(display, drawable, gc, layoutPtr->tkfont, chunkPtr->start, chunkPtr->numBytes, firstByte - chunkPtr->start, lastByte - firstByte, (int)(x + dx), (int)(y + dy)); } else { TkpDrawAngledCharsInContext(display, drawable, gc, layoutPtr->tkfont, chunkPtr->start, chunkPtr->numBytes, firstByte - chunkPtr->start, lastByte - firstByte, x+dx, y+dy, angle); } #else /* !TK_DRAW_IN_CONTEXT */ dx = cosA * (chunkPtr->x + drawX) + sinA * (chunkPtr->y); dy = -sinA * (chunkPtr->x + drawX) + cosA * (chunkPtr->y); if (angle == 0.0) { Tk_DrawChars(display, drawable, gc, layoutPtr->tkfont, firstByte, lastByte - firstByte, (int)(x + dx), (int)(y + dy)); } else { TkDrawAngledChars(display, drawable, gc, layoutPtr->tkfont, firstByte, lastByte - firstByte, x+dx, y+dy, angle); } #endif /* TK_DRAW_IN_CONTEXT */ } firstChar -= chunkPtr->numChars; lastChar -= chunkPtr->numChars; if (lastChar <= 0) { break; } chunkPtr++; } } /* *--------------------------------------------------------------------------- * * Tk_UnderlineTextLayout -- * * Use the information in the Tk_TextLayout token to display an underline * below an individual character. This function does not draw the text, * just the underline. * * This function is useful for simple widgets that need to display * single-font, multi-line text with an individual character underlined * and want Tk to handle the details. To display larger amounts of * underlined text, construct and use an underlined font. * * Results: * None. * * Side effects: * Underline drawn on the screen. * *--------------------------------------------------------------------------- */ void Tk_UnderlineTextLayout( Display *display, /* Display on which to draw. */ Drawable drawable, /* Window or pixmap in which to draw. */ GC gc, /* Graphics context to use for drawing text. */ Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ int x, int y, /* Upper-left hand corner of rectangle in * which to draw (pixels). */ int underline) /* Index of the single character to underline, * or INT_MIN for no underline. */ { int xx, yy, width, height; if ((Tk_CharBbox(layout, underline, &xx, &yy, &width, &height) != 0) && (width != 0)) { TextLayout *layoutPtr = (TextLayout *) layout; TkFont *fontPtr = (TkFont *) layoutPtr->tkfont; XFillRectangle(display, drawable, gc, x + xx, y + yy + fontPtr->fm.ascent + fontPtr->underlinePos, (unsigned) width, (unsigned) fontPtr->underlineHeight); } } void TkUnderlineAngledTextLayout( Display *display, /* Display on which to draw. */ Drawable drawable, /* Window or pixmap in which to draw. */ GC gc, /* Graphics context to use for drawing * text. */ Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ int x, int y, /* Upper-left hand corner of rectangle in * which to draw (pixels). */ double angle, int underline) /* Index of the single character to underline, * or INT_MIN for no underline. */ { int xx, yy, width, height; if (angle == 0.0) { Tk_UnderlineTextLayout(display, drawable, gc, layout, x,y, underline); return; } if ((Tk_CharBbox(layout, underline, &xx, &yy, &width, &height) != 0) && (width != 0)) { TextLayout *layoutPtr = (TextLayout *) layout; TkFont *fontPtr = (TkFont *) layoutPtr->tkfont; double sinA = sin(angle*PI/180), cosA = cos(angle*PI/180); double dy = yy + fontPtr->fm.ascent + fontPtr->underlinePos; XPoint points[5]; /* * Note that we're careful to only round a double value once, which * minimizes roundoff errors. */ points[0].x = x + ROUND16(xx*cosA + dy*sinA); points[0].y = y + ROUND16(dy*cosA - xx*sinA); points[1].x = x + ROUND16(xx*cosA + dy*sinA + width*cosA); points[1].y = y + ROUND16(dy*cosA - xx*sinA - width*sinA); if (fontPtr->underlineHeight == 1) { /* * Thin underlines look better when rotated when drawn as a line * rather than a rectangle; the rasterizer copes better. */ XDrawLines(display, drawable, gc, points, 2, CoordModeOrigin); } else { points[2].x = x + ROUND16(xx*cosA + dy*sinA + width*cosA + fontPtr->underlineHeight*sinA); points[2].y = y + ROUND16(dy*cosA - xx*sinA - width*sinA + fontPtr->underlineHeight*cosA); points[3].x = x + ROUND16(xx*cosA + dy*sinA + fontPtr->underlineHeight*sinA); points[3].y = y + ROUND16(dy*cosA - xx*sinA + fontPtr->underlineHeight*cosA); points[4].x = points[0].x; points[4].y = points[0].y; XFillPolygon(display, drawable, gc, points, 5, Complex, CoordModeOrigin); XDrawLines(display, drawable, gc, points, 5, CoordModeOrigin); } } } /* *--------------------------------------------------------------------------- * * Tk_PointToChar -- * * Use the information in the Tk_TextLayout token to determine the * character closest to the given point. The point must be specified with * respect to the upper-left hand corner of the text layout, which is * considered to be located at (0, 0). * * Any point whose y-value is less that 0 will be considered closest to * the first character in the text layout; any point whose y-value is * greater than the height of the text layout will be considered closest * to the last character in the text layout. * * Any point whose x-value is less than 0 will be considered closest to * the first character on that line; any point whose x-value is greater * than the width of the text layout will be considered closest to the * last character on that line. * * Results: * The return value is the index of the character that was closest to the * point. Given a text layout with no characters, the value 0 will always * be returned, referring to a hypothetical zero-width placeholder * character. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tk_PointToChar( Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ int x, int y) /* Coordinates of point to check, with respect * to the upper-left corner of the text * layout. */ { TextLayout *layoutPtr = (TextLayout *) layout; LayoutChunk *chunkPtr, *lastPtr; TkFont *fontPtr; int i, n, dummy, baseline, pos, numChars; if (y < 0) { /* * Point lies above any line in this layout. Return the index of the * first char. */ return 0; } /* * Find which line contains the point. */ fontPtr = (TkFont *) layoutPtr->tkfont; lastPtr = chunkPtr = layoutPtr->chunks; numChars = 0; for (i = 0; i < layoutPtr->numChunks; i++) { baseline = chunkPtr->y; if (y < baseline + fontPtr->fm.descent) { if (x < chunkPtr->x) { /* * Point is to the left of all chunks on this line. Return the * index of the first character on this line. */ return numChars; } if (x >= layoutPtr->width) { /* * If point lies off right side of the text layout, return the * last char in the last chunk on this line. Without this, it * might return the index of the first char that was located * outside of the text layout. */ x = INT_MAX; } /* * Examine all chunks on this line to see which one contains the * specified point. */ lastPtr = chunkPtr; while ((i < layoutPtr->numChunks) && (chunkPtr->y == baseline)) { if (x < chunkPtr->x + chunkPtr->totalWidth) { /* * Point falls on one of the characters in this chunk. */ if (chunkPtr->numDisplayChars < 0) { /* * This is a special chunk that encapsulates a single * tab or newline char. */ return numChars; } n = Tk_MeasureChars((Tk_Font) fontPtr, chunkPtr->start, chunkPtr->numBytes, x - chunkPtr->x, 0, &dummy); return numChars + Tcl_NumUtfChars(chunkPtr->start, n); } numChars += chunkPtr->numChars; lastPtr = chunkPtr; chunkPtr++; i++; } /* * Point is to the right of all chars in all the chunks on this * line. Return the index just past the last char in the last * chunk on this line. */ pos = numChars; if (i < layoutPtr->numChunks) { pos--; } return pos; } numChars += chunkPtr->numChars; lastPtr = chunkPtr; chunkPtr++; } /* * Point lies below any line in this text layout. Return the index just * past the last char. */ return (lastPtr->start + lastPtr->numChars) - layoutPtr->string; } /* *--------------------------------------------------------------------------- * * Tk_CharBbox -- * * Use the information in the Tk_TextLayout token to return the bounding * box for the character specified by index. * * The width of the bounding box is the advance width of the character, * and does not include and left- or right-bearing. Any character that * extends partially outside of the text layout is considered to be * truncated at the edge. Any character which is located completely * outside of the text layout is considered to be zero-width and pegged * against the edge. * * The height of the bounding box is the line height for this font, * extending from the top of the ascent to the bottom of the descent. * Information about the actual height of the individual letter is not * available. * * A text layout that contains no characters is considered to contain a * single zero-width placeholder character. * * Results: * The return value is 0 if the index did not specify a character in the * text layout, or non-zero otherwise. In that case, *bbox is filled with * the bounding box of the character. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tk_CharBbox( Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ Tcl_Size index, /* The index of the character whose bbox is * desired. Negative means count backwards. */ int *xPtr, int *yPtr, /* Filled with the upper-left hand corner, in * pixels, of the bounding box for the * character specified by index, if * non-NULL. */ int *widthPtr, int *heightPtr) /* Filled with the width and height of the * bounding box for the character specified by * index, if non-NULL. */ { TextLayout *layoutPtr = (TextLayout *) layout; LayoutChunk *chunkPtr = layoutPtr->chunks; int i, x = 0, w; Tk_Font tkfont; TkFont *fontPtr; const char *end; if (index < 0) { for (i = 0; i < layoutPtr->numChunks; i++) { index += (chunkPtr + i)->numChars; } if (index < 0) { return 0; } } tkfont = layoutPtr->tkfont; fontPtr = (TkFont *) tkfont; for (i = 0; i < layoutPtr->numChunks; i++) { if (chunkPtr->numDisplayChars < 0) { if (index == 0) { x = chunkPtr->x; w = chunkPtr->totalWidth; goto check; } } else if (index < chunkPtr->numChars) { end = Tcl_UtfAtIndex(chunkPtr->start, index); if (xPtr != NULL) { Tk_MeasureChars(tkfont, chunkPtr->start, end - chunkPtr->start, -1, 0, &x); x += chunkPtr->x; } if (widthPtr != NULL) { int ch; Tk_MeasureChars(tkfont, end, Tcl_UtfToUniChar(end, &ch), -1, 0, &w); } goto check; } index -= chunkPtr->numChars; chunkPtr++; } if (index != 0) { return 0; } /* * Special case to get location just past last char in layout. */ chunkPtr--; x = chunkPtr->x + chunkPtr->totalWidth; w = 0; /* * Ensure that the bbox lies within the text layout. This forces all chars * that extend off the right edge of the text layout to have truncated * widths, and all chars that are completely off the right edge of the * text layout to peg to the edge and have 0 width. */ check: if (yPtr != NULL) { *yPtr = chunkPtr->y - fontPtr->fm.ascent; } if (heightPtr != NULL) { *heightPtr = fontPtr->fm.ascent + fontPtr->fm.descent; } if (x > layoutPtr->width) { x = layoutPtr->width; } if (xPtr != NULL) { *xPtr = x; } if (widthPtr != NULL) { if (x + w > layoutPtr->width) { w = layoutPtr->width - x; } *widthPtr = w; } return 1; } /* *--------------------------------------------------------------------------- * * Tk_DistanceToTextLayout -- * * Computes the distance in pixels from the given point to the given text * layout. Non-displaying space characters that occur at the end of * individual lines in the text layout are ignored for hit detection * purposes. * * Results: * The return value is 0 if the point (x, y) is inside the text layout. * If the point isn't inside the text layout then the return value is the * distance in pixels from the point to the text item. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tk_DistanceToTextLayout( Tk_TextLayout layout, /* Layout information, from a previous call * to Tk_ComputeTextLayout(). */ int x, int y) /* Coordinates of point to check, with respect * to the upper-left corner of the text layout * (in pixels). */ { int i, x1, x2, y1, y2, xDiff, yDiff, dist, minDist, ascent, descent; TextLayout *layoutPtr = (TextLayout *) layout; LayoutChunk *chunkPtr; TkFont *fontPtr; fontPtr = (TkFont *) layoutPtr->tkfont; ascent = fontPtr->fm.ascent; descent = fontPtr->fm.descent; minDist = 0; chunkPtr = layoutPtr->chunks; for (i = 0; i < layoutPtr->numChunks; i++) { if (chunkPtr->start[0] == '\n') { /* * Newline characters are not counted when computing distance (but * tab characters would still be considered). */ chunkPtr++; continue; } x1 = chunkPtr->x; y1 = chunkPtr->y - ascent; x2 = chunkPtr->x + chunkPtr->displayWidth; y2 = chunkPtr->y + descent; if (x < x1) { xDiff = x1 - x; } else if (x >= x2) { xDiff = x - x2 + 1; } else { xDiff = 0; } if (y < y1) { yDiff = y1 - y; } else if (y >= y2) { yDiff = y - y2 + 1; } else { yDiff = 0; } if ((xDiff == 0) && (yDiff == 0)) { return 0; } dist = (int) hypot((double) xDiff, (double) yDiff); if ((dist < minDist) || (minDist == 0)) { minDist = dist; } chunkPtr++; } return minDist; } /* *--------------------------------------------------------------------------- * * Tk_IntersectTextLayout -- * * Determines whether a text layout lies entirely inside, entirely * outside, or overlaps a given rectangle. Non-displaying space * characters that occur at the end of individual lines in the text * layout are ignored for intersection calculations. * * Results: * The return value is -1 if the text layout is entirely outside of the * rectangle, 0 if it overlaps, and 1 if it is entirely inside of the * rectangle. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int Tk_IntersectTextLayout( Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ int x, int y, /* Upper-left hand corner, in pixels, of * rectangular area to compare with text * layout. Coordinates are with respect to the * upper-left hand corner of the text layout * itself. */ int width, int height) /* The width and height of the above * rectangular area, in pixels. */ { int result, i, x1, y1, x2, y2; TextLayout *layoutPtr = (TextLayout *) layout; LayoutChunk *chunkPtr; TkFont *fontPtr; int left, top, right, bottom; /* * Scan the chunks one at a time, seeing whether each is entirely in, * entirely out, or overlapping the rectangle. If an overlap is detected, * return immediately; otherwise wait until all chunks have been processed * and see if they were all inside or all outside. */ chunkPtr = layoutPtr->chunks; fontPtr = (TkFont *) layoutPtr->tkfont; left = x; top = y; right = x + width; bottom = y + height; result = 0; for (i = 0; i < layoutPtr->numChunks; i++) { if ((chunkPtr->start[0] == '\n') || (chunkPtr->numBytes == 0)) { /* * Newline characters and empty chunks are not counted when * computing area intersection (but tab characters would still be * considered). */ chunkPtr++; continue; } x1 = chunkPtr->x; y1 = chunkPtr->y - fontPtr->fm.ascent; x2 = chunkPtr->x + chunkPtr->displayWidth; y2 = chunkPtr->y + fontPtr->fm.descent; if ((right < x1) || (left >= x2) || (bottom < y1) || (top >= y2)) { if (result == 1) { return 0; } result = -1; } else if ((x1 < left) || (x2 >= right) || (y1 < top) || (y2 >= bottom)) { return 0; } else if (result == -1) { return 0; } else { result = 1; } chunkPtr++; } return result; } /* *--------------------------------------------------------------------------- * * TkIntersectAngledTextLayout -- * * Determines whether a text layout that has been turned by an angle * about its top-left coordinae lies entirely inside, entirely outside, * or overlaps a given rectangle. Non-displaying space characters that * occur at the end of individual lines in the text layout are ignored * for intersection calculations. * * Results: * The return value is -1 if the text layout is entirely outside of the * rectangle, 0 if it overlaps, and 1 if it is entirely inside of the * rectangle. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static inline int PointInQuadrilateral( double qx[], double qy[], double x, double y) { int i; for (i=0 ; i<4 ; i++) { double sideDX = qx[(i+1)%4] - qx[i]; double sideDY = qy[(i+1)%4] - qy[i]; double dx = x - qx[i]; double dy = y - qy[i]; if (sideDX*dy < sideDY*dx) { return 0; } } return 1; } static inline int SidesIntersect( double ax1, double ay1, double ax2, double ay2, double bx1, double by1, double bx2, double by2) { #if 0 /* http://www.freelunchdesign.com/cgi-bin/codwiki.pl?DiscussionTopics/CollideMeUpBaby */ double a1, b1, c1, a2, b2, c2, r1, r2, r3, r4, denom; a1 = ay2 - ay1; b1 = ax1 - ax2; c1 = (ax2 * ay1) - (ax1 * ay2); r3 = (a1 * bx1) + (b1 * by1) + c1; r4 = (a1 * bx2) + (b1 * by2) + c1; if ((r3 != 0.0) && (r4 != 0.0) && (r3*r4 > 0.0)) { return 0; } a2 = by2 - by1; b2 = bx1 - bx2; c2 = (bx2 * by1) - (bx1 * by2); r1 = (a2 * ax1) + (b2 * ay1) + c2; r2 = (a2 * ax2) + (b2 * ay2) + c2; if ((r1 != 0.0) && (r2 != 0.0) && (r1*r2 > 0.0)) { return 0; } denom = (a1 * b2) - (a2 * b1); return (denom != 0.0); #else /* * A more efficient version. Two line segments intersect if, when seen * from the perspective of one line, the two endpoints of the other * segment lie on opposite sides of the line, and vice versa. "Lie on * opposite sides" is computed by taking the cross products and seeing if * they are of opposite signs. */ double dx, dy, dx1, dy1; dx = ax2 - ax1; dy = ay2 - ay1; dx1 = bx1 - ax1; dy1 = by1 - ay1; if ((dx*dy1-dy*dx1 > 0.0) == (dx*(by2-ay1)-dy*(bx2-ax1) > 0.0)) { return 0; } dx = bx2 - bx1; dy = by2 - by1; if ((dy*dx1-dx*dy1 > 0.0) == (dx*(ay2-by1)-dy*(ax2-bx1) > 0.0)) { return 0; } return 1; #endif } int TkIntersectAngledTextLayout( Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ int x, int y, /* Upper-left hand corner, in pixels, of * rectangular area to compare with text * layout. Coordinates are with respect to the * upper-left hand corner of the text layout * itself. */ int width, int height, /* The width and height of the above * rectangular area, in pixels. */ double angle) { int i, x1, y1, x2, y2; TextLayout *layoutPtr; LayoutChunk *chunkPtr; TkFont *fontPtr; double c = cos(angle * PI/180.0), s = sin(angle * PI/180.0); double rx[4], ry[4]; if (angle == 0.0) { return Tk_IntersectTextLayout(layout, x, y, width, height); } /* * Compute the coordinates of the rectangle, rotated into text layout * space. */ rx[0] = x*c - y*s; ry[0] = y*c + x*s; rx[1] = (x+width)*c - y*s; ry[1] = y*c + (x+width)*s; rx[2] = (x+width)*c - (y+height)*s; ry[2] = (y+height)*c + (x+width)*s; rx[3] = x*c - (y+height)*s; ry[3] = (y+height)*c + x*s; /* * Want to know if all chunks are inside the rectangle, or if there is any * overlap. First, we check to see if all chunks are inside; if and only * if they are, we're in the "inside" case. */ layoutPtr = (TextLayout *) layout; chunkPtr = layoutPtr->chunks; fontPtr = (TkFont *) layoutPtr->tkfont; for (i=0 ; inumChunks ; i++,chunkPtr++) { if (chunkPtr->start[0] == '\n') { /* * Newline characters are not counted when computing area * intersection (but tab characters would still be considered). */ continue; } x1 = chunkPtr->x; y1 = chunkPtr->y - fontPtr->fm.ascent; x2 = chunkPtr->x + chunkPtr->displayWidth; y2 = chunkPtr->y + fontPtr->fm.descent; if ( !PointInQuadrilateral(rx, ry, x1, y1) || !PointInQuadrilateral(rx, ry, x2, y1) || !PointInQuadrilateral(rx, ry, x2, y2) || !PointInQuadrilateral(rx, ry, x1, y2)) { goto notInside; } } return 1; /* * Next, check to see if all the points of the rectangle are inside a * single chunk; if they are, we're in an "overlap" case. */ notInside: chunkPtr = layoutPtr->chunks; for (i=0 ; inumChunks ; i++,chunkPtr++) { double cx[4], cy[4]; if (chunkPtr->start[0] == '\n') { /* * Newline characters are not counted when computing area * intersection (but tab characters would still be considered). */ continue; } cx[0] = cx[3] = chunkPtr->x; cy[0] = cy[1] = chunkPtr->y - fontPtr->fm.ascent; cx[1] = cx[2] = chunkPtr->x + chunkPtr->displayWidth; cy[2] = cy[3] = chunkPtr->y + fontPtr->fm.descent; if ( PointInQuadrilateral(cx, cy, rx[0], ry[0]) && PointInQuadrilateral(cx, cy, rx[1], ry[1]) && PointInQuadrilateral(cx, cy, rx[2], ry[2]) && PointInQuadrilateral(cx, cy, rx[3], ry[3])) { return 0; } } /* * If we're overlapping now, we must be partially in and out of at least * one chunk. If that is the case, there must be one line segment of the * rectangle that is touching or crossing a line segment of a chunk. */ chunkPtr = layoutPtr->chunks; for (i=0 ; inumChunks ; i++,chunkPtr++) { int j; if (chunkPtr->start[0] == '\n') { /* * Newline characters are not counted when computing area * intersection (but tab characters would still be considered). */ continue; } x1 = chunkPtr->x; y1 = chunkPtr->y - fontPtr->fm.ascent; x2 = chunkPtr->x + chunkPtr->displayWidth; y2 = chunkPtr->y + fontPtr->fm.descent; for (j=0 ; j<4 ; j++) { int k = (j+1) % 4; if ( SidesIntersect(rx[j],ry[j], rx[k],ry[k], x1,y1, x2,y1) || SidesIntersect(rx[j],ry[j], rx[k],ry[k], x2,y1, x2,y2) || SidesIntersect(rx[j],ry[j], rx[k],ry[k], x2,y2, x1,y2) || SidesIntersect(rx[j],ry[j], rx[k],ry[k], x1,y2, x1,y1)) { return 0; } } } /* * They must be wholly non-overlapping. */ return -1; } /* *--------------------------------------------------------------------------- * * Tk_TextLayoutToPostscript -- * * Outputs the contents of a text layout in Postscript format. The set of * lines in the text layout will be rendered by the user supplied * Postscript function. The function should be of the form: * * justify x y string function -- * * Justify is -1, 0, or 1, depending on whether the following string * should be left, center, or right justified, x and y is the location * for the origin of the string, string is the sequence of characters to * be printed, and function is the name of the caller-provided function; * the function should leave nothing on the stack. * * The meaning of the origin of the string (x and y) depends on the * justification. For left justification, x is where the left edge of the * string should appear. For center justification, x is where the center * of the string should appear. And for right justification, x is where * the right edge of the string should appear. This behavior is necessary * because, for example, right justified text on the screen is justified * with screen metrics. The same string needs to be justified with * printer metrics on the printer to appear in the correct place with * respect to other similarly justified strings. In all circumstances, y * is the location of the baseline for the string. * * Results: * The interp's result is modified to hold the Postscript code that will * render the text layout. * * Side effects: * None. * *--------------------------------------------------------------------------- */ void Tk_TextLayoutToPostscript( Tcl_Interp *interp, /* Filled with Postscript code. */ Tk_TextLayout layout) /* The layout to be rendered. */ { TextLayout *layoutPtr = (TextLayout *) layout; LayoutChunk *chunkPtr = layoutPtr->chunks; int baseline = chunkPtr->y; Tcl_Obj *psObj = Tcl_NewObj(); int i, j; Tcl_Size len; const char *p, *glyphname; char uindex[5], c, *ps; int ch; Tcl_AppendToObj(psObj, "[(", TCL_INDEX_NONE); for (i = 0; i < layoutPtr->numChunks; i++, chunkPtr++) { if (baseline != chunkPtr->y) { Tcl_AppendToObj(psObj, ")]\n[(", TCL_INDEX_NONE); baseline = chunkPtr->y; } if (chunkPtr->numDisplayChars <= 0) { if (chunkPtr->start[0] == '\t') { Tcl_AppendToObj(psObj, "\\t", TCL_INDEX_NONE); } continue; } for (p=chunkPtr->start, j=0; jnumDisplayChars; j++) { /* * INTL: We only handle symbols that have an encoding as a glyph * from the standard set defined by Adobe. The rest get punted. * Eventually this should be revised to handle more sophsticiated * international postscript fonts. */ p += Tcl_UtfToUniChar(p, &ch); if ((ch == '(') || (ch == ')') || (ch == '\\') || (ch < 0x20)) { /* * Tricky point: the "03" is necessary in the snprintf below, * so that a full three digits of octal are always generated. * Without the "03", a number following this sequence could be * interpreted by Postscript as part of this sequence. */ Tcl_AppendPrintfToObj(psObj, "\\%03o", ch); continue; } else if (ch <= 0x7f) { /* * Normal ASCII character. */ c = (char) ch; Tcl_AppendToObj(psObj, &c, 1); continue; } /* * This character doesn't belong to the ASCII character set, so we * use the full glyph name. */ if (ch > 0xffff) { goto noMapping; } snprintf(uindex, sizeof(uindex), "%04X", ch); /* endianness? */ glyphname = Tcl_GetVar2(interp, "::tk::psglyphs", uindex, 0); if (glyphname) { ps = Tcl_GetStringFromObj(psObj, &len); if (ps[len-1] == '(') { /* * In-place edit. Ewww! */ ps[len-1] = '/'; } else { Tcl_AppendToObj(psObj, ")/", TCL_INDEX_NONE); } Tcl_AppendToObj(psObj, glyphname, TCL_INDEX_NONE); Tcl_AppendToObj(psObj, "(", TCL_INDEX_NONE); } else { /* * No known mapping for the character into the space of * PostScript glyphs. Ignore it. :-( */ noMapping: ; #ifdef TK_DEBUG_POSTSCRIPT_OUTPUT fprintf(stderr, "Warning: no mapping to PostScript " "glyphs for \\u%04x\n", ch); #endif } } } Tcl_AppendToObj(psObj, ")]\n", TCL_INDEX_NONE); Tcl_AppendObjToObj(Tcl_GetObjResult(interp), psObj); Tcl_DecrRefCount(psObj); } /* *--------------------------------------------------------------------------- * * ConfigAttributesObj -- * * Process command line options to fill in fields of a properly * initialized font attributes structure. * * Results: * A standard Tcl return value. If TCL_ERROR is returned, an error * message will be left in interp's result object. * * Side effects: * The fields of the font attributes structure get filled in with * information from argc/argv. If an error occurs while parsing, the font * attributes structure will contain all modifications specified in the * command line options up to the point of the error. * *--------------------------------------------------------------------------- */ static int ConfigAttributesObj( Tcl_Interp *interp, /* Interp for error return. */ TCL_UNUSED(Tk_Window), /* For display on which font will be used. */ Tcl_Size objc, /* Number of elements in argv. */ Tcl_Obj *const objv[], /* Command line options. */ TkFontAttributes *faPtr) /* Font attributes structure whose fields are * to be modified. Structure must already be * properly initialized. */ { Tcl_Size i; int n, index; Tcl_Obj *optionPtr, *valuePtr; const char *value; for (i = 0; i < objc; i += 2) { optionPtr = objv[i]; if (Tcl_GetIndexFromObj(interp, optionPtr, fontOpt, "option", 1, &index) != TCL_OK) { return TCL_ERROR; } if ((i+2 >= objc) && (objc & 1)) { /* * This test occurs after Tcl_GetIndexFromObj() so that "font * create xyz -xyz" will return the error message that "-xyz" is a * bad option, rather than that the value for "-xyz" is missing. */ if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "value for \"%s\" option missing", Tcl_GetString(optionPtr))); Tcl_SetErrorCode(interp, "TK", "FONT", "NO_ATTRIBUTE", NULL); } return TCL_ERROR; } valuePtr = objv[i + 1]; switch (index) { case FONT_FAMILY: value = Tcl_GetString(valuePtr); faPtr->family = Tk_GetUid(value); break; case FONT_SIZE: if (Tcl_GetIntFromObj(interp, valuePtr, &n) != TCL_OK) { return TCL_ERROR; } faPtr->size = (double)n; break; case FONT_WEIGHT: n = TkFindStateNumObj(interp, optionPtr, weightMap, valuePtr); if (n == TK_FW_UNKNOWN) { return TCL_ERROR; } faPtr->weight = n; break; case FONT_SLANT: n = TkFindStateNumObj(interp, optionPtr, slantMap, valuePtr); if (n == TK_FS_UNKNOWN) { return TCL_ERROR; } faPtr->slant = n; break; case FONT_UNDERLINE: if (Tcl_GetBooleanFromObj(interp, valuePtr, &n) != TCL_OK) { return TCL_ERROR; } faPtr->underline = n; break; case FONT_OVERSTRIKE: if (Tcl_GetBooleanFromObj(interp, valuePtr, &n) != TCL_OK) { return TCL_ERROR; } faPtr->overstrike = n; break; } } return TCL_OK; } /* *--------------------------------------------------------------------------- * * GetAttributeInfoObj -- * * Return information about the font attributes as a Tcl list. * * Results: * The return value is TCL_OK if the objPtr was non-NULL and specified a * valid font attribute, TCL_ERROR otherwise. If TCL_OK is returned, the * interp's result object is modified to hold a description of either the * current value of a single option, or a list of all options and their * current values for the given font attributes. If TCL_ERROR is * returned, the interp's result is set to an error message describing * that the objPtr did not refer to a valid option. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int GetAttributeInfoObj( Tcl_Interp *interp, /* Interp to hold result. */ const TkFontAttributes *faPtr, /* The font attributes to inspect. */ Tcl_Obj *objPtr) /* If non-NULL, indicates the single option * whose value is to be returned. Otherwise * information is returned for all options. */ { int i, index, start, end; const char *str; Tcl_Obj *valuePtr, *resultPtr = NULL; start = 0; end = FONT_NUMFIELDS; if (objPtr != NULL) { if (Tcl_GetIndexFromObj(interp, objPtr, fontOpt, "option", TCL_EXACT, &index) != TCL_OK) { return TCL_ERROR; } start = index; end = index + 1; } valuePtr = NULL; if (objPtr == NULL) { resultPtr = Tcl_NewObj(); } for (i = start; i < end; i++) { switch (i) { case FONT_FAMILY: str = faPtr->family; valuePtr = Tcl_NewStringObj(str, ((str == NULL) ? 0 : -1)); break; case FONT_SIZE: if (faPtr->size >= 0.0) { valuePtr = Tcl_NewWideIntObj((Tcl_WideInt)(faPtr->size + 0.5)); } else { valuePtr = Tcl_NewWideIntObj(-(Tcl_WideInt)(-faPtr->size + 0.5)); } break; case FONT_WEIGHT: str = TkFindStateString(weightMap, faPtr->weight); valuePtr = Tcl_NewStringObj(str, TCL_INDEX_NONE); break; case FONT_SLANT: str = TkFindStateString(slantMap, faPtr->slant); valuePtr = Tcl_NewStringObj(str, TCL_INDEX_NONE); break; case FONT_UNDERLINE: valuePtr = Tcl_NewBooleanObj(faPtr->underline); break; case FONT_OVERSTRIKE: valuePtr = Tcl_NewBooleanObj(faPtr->overstrike); break; } if (objPtr != NULL) { Tcl_SetObjResult(interp, valuePtr); return TCL_OK; } Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(fontOpt[i], TCL_INDEX_NONE)); Tcl_ListObjAppendElement(NULL, resultPtr, valuePtr); } Tcl_SetObjResult(interp, resultPtr); return TCL_OK; } /* *--------------------------------------------------------------------------- * * Tk_FontGetDescription -- * * Return information about the font description as a Tcl list. One * possible result is "{{DejaVu Sans} -16 bold underline}". * * Results: * The list of descriptions. * * Side effects: * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tk_FontGetDescription( Tk_Font tkfont) /* Font whose description is desired. */ { const TkFontAttributes *faPtr = GetFontAttributes(tkfont); Tcl_Obj *resultPtr = Tcl_NewObj(); const char *str; str = faPtr->family; Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(str, str ? -1 : 0)); if (faPtr->size >= 0.0) { Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewWideIntObj((int)(faPtr->size + 0.5))); } else { Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewWideIntObj(-(int)(-faPtr->size + 0.5))); } if (faPtr->weight != TK_FW_NORMAL) { str = TkFindStateString(weightMap, faPtr->weight); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(str, TCL_INDEX_NONE)); } if (faPtr->slant != TK_FS_ROMAN) { str = TkFindStateString(slantMap, faPtr->slant); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(str, TCL_INDEX_NONE)); } if (faPtr->underline) { str = TkFindStateString(underlineMap, faPtr->underline); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(str, TCL_INDEX_NONE)); } if (faPtr->overstrike) { str = TkFindStateString(overstrikeMap, faPtr->overstrike); Tcl_ListObjAppendElement(NULL, resultPtr, Tcl_NewStringObj(str, TCL_INDEX_NONE)); } return resultPtr; } /* *--------------------------------------------------------------------------- * * ParseFontNameObj -- * * Converts a object into a set of font attributes that can be used to * construct a font. * * The string rep of the object can be one of the following forms: * XLFD (see X documentation) * "family [size] [style1 [style2 ...]" * "-option value [-option value ...]" * * Results: * The return value is TCL_ERROR if the object was syntactically invalid. * In that case an error message is left in interp's result object. * Otherwise, fills the font attribute buffer with the values parsed from * the string and returns TCL_OK; * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int ParseFontNameObj( Tcl_Interp *interp, /* Interp for error return. */ Tk_Window tkwin, /* For display on which font is used. */ Tcl_Obj *objPtr, /* Parseable font description object. */ TkFontAttributes *faPtr) /* Filled with attributes parsed from font * name. Any attributes that were not * specified in font name are filled with * default values. */ { const char *dash; int result, n; Tcl_Size objc, i; Tcl_Obj **objv; const char *string; TkInitFontAttributes(faPtr); string = Tcl_GetString(objPtr); if (*string == '-') { /* * This may be an XLFD or an "-option value" string. * * If the string begins with "-*" or a "-foundry-family-*" pattern, * then consider it an XLFD. */ if (string[1] == '*') { goto xlfd; } dash = strchr(string + 1, '-'); if ((dash != NULL) && !isspace(UCHAR(dash[-1]))) { /* INTL: ISO space */ goto xlfd; } if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { return TCL_ERROR; } return ConfigAttributesObj(interp, tkwin, objc, objv, faPtr); } if (*string == '*') { /* * This is appears to be an XLFD. Under Unix, all valid XLFDs were * already handled by TkpGetNativeFont. If we are here, either we have * something that initially looks like an XLFD but isn't or we have * encountered an XLFD on Windows or Mac. */ xlfd: result = TkFontParseXLFD(string, faPtr, NULL); if (result == TCL_OK) { return TCL_OK; } /* * If the string failed to parse but was considered to be a XLFD * then it may be a "-option value" string with a hyphenated family * name as per bug 2791352 */ if (Tcl_ListObjGetElements(interp, objPtr, &objc, &objv) != TCL_OK) { return TCL_ERROR; } if (ConfigAttributesObj(interp, tkwin, objc, objv, faPtr) == TCL_OK) { return TCL_OK; } } /* * Wasn't an XLFD or "-option value" string. Try it as a "font size style" * list. */ if ((Tcl_ListObjGetElements(NULL, objPtr, &objc, &objv) != TCL_OK) || (objc < 1)) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "font \"%s\" doesn't exist", string)); Tcl_SetErrorCode(interp, "TK", "LOOKUP", "FONT", string, NULL); } return TCL_ERROR; } faPtr->family = Tk_GetUid(Tcl_GetString(objv[0])); if (objc > 1) { if (Tcl_GetIntFromObj(interp, objv[1], &n) != TCL_OK) { return TCL_ERROR; } faPtr->size = (double)n; } i = 2; if (objc == 3) { if (Tcl_ListObjGetElements(interp, objv[2], &objc, &objv) != TCL_OK) { return TCL_ERROR; } i = 0; } for ( ; i < objc; i++) { n = TkFindStateNumObj(NULL, NULL, weightMap, objv[i]); if (n != TK_FW_UNKNOWN) { faPtr->weight = n; continue; } n = TkFindStateNumObj(NULL, NULL, slantMap, objv[i]); if (n != TK_FS_UNKNOWN) { faPtr->slant = n; continue; } n = TkFindStateNumObj(NULL, NULL, underlineMap, objv[i]); if (n != 0) { faPtr->underline = n; continue; } n = TkFindStateNumObj(NULL, NULL, overstrikeMap, objv[i]); if (n != 0) { faPtr->overstrike = n; continue; } /* * Unknown style. */ if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "unknown font style \"%s\"", Tcl_GetString(objv[i]))); Tcl_SetErrorCode(interp, "TK", "LOOKUP", "FONT_STYLE", Tcl_GetString(objv[i]), NULL); } return TCL_ERROR; } return TCL_OK; } /* *--------------------------------------------------------------------------- * * NewChunk -- * * Helper function for Tk_ComputeTextLayout(). Encapsulates a measured * set of characters in a chunk that can be quickly drawn. * * Results: * A pointer to the new chunk in the text layout. * * Side effects: * The text layout is reallocated to hold more chunks as necessary. * * Currently, Tk_ComputeTextLayout() stores contiguous ranges of "normal" * characters in a chunk, along with individual tab and newline chars in * their own chunks. All characters in the text layout are accounted for. * *--------------------------------------------------------------------------- */ static LayoutChunk * NewChunk( TextLayout **layoutPtrPtr, int *maxPtr, const char *start, Tcl_Size numBytes, int curX, int newX, int y) { TextLayout *layoutPtr; LayoutChunk *chunkPtr; int maxChunks, numChars; size_t s; layoutPtr = *layoutPtrPtr; maxChunks = *maxPtr; if (layoutPtr->numChunks == maxChunks) { maxChunks *= 2; s = offsetof(TextLayout, chunks) + (maxChunks * sizeof(LayoutChunk)); layoutPtr = (TextLayout *)ckrealloc(layoutPtr, s); *layoutPtrPtr = layoutPtr; *maxPtr = maxChunks; } numChars = Tcl_NumUtfChars(start, numBytes); chunkPtr = &layoutPtr->chunks[layoutPtr->numChunks]; chunkPtr->start = start; chunkPtr->numBytes = numBytes; chunkPtr->numChars = numChars; chunkPtr->numDisplayChars = numChars; chunkPtr->x = curX; chunkPtr->y = y; chunkPtr->totalWidth = newX - curX; chunkPtr->displayWidth = newX - curX; layoutPtr->numChunks++; return chunkPtr; } /* *--------------------------------------------------------------------------- * * TkFontParseXLFD -- * * Break up a fully specified XLFD into a set of font attributes. * * Results: * Return value is TCL_ERROR if string was not a fully specified XLFD. * Otherwise, fills font attribute buffer with the values parsed from the * XLFD and returns TCL_OK. * * Side effects: * None. * *--------------------------------------------------------------------------- */ int TkFontParseXLFD( const char *string, /* Parseable font description string. */ TkFontAttributes *faPtr, /* Filled with attributes parsed from font * name. Any attributes that were not * specified in font name are filled with * default values. */ TkXLFDAttributes *xaPtr) /* Filled with X-specific attributes parsed * from font name. Any attributes that were * not specified in font name are filled with * default values. May be NULL if such * information is not desired. */ { char *src; const char *str; int i, j; char *field[XLFD_NUMFIELDS + 2]; Tcl_DString ds; TkXLFDAttributes xa; if (xaPtr == NULL) { xaPtr = &xa; } TkInitFontAttributes(faPtr); TkInitXLFDAttributes(xaPtr); memset(field, '\0', sizeof(field)); str = string; if (*str == '-') { str++; } Tcl_DStringInit(&ds); Tcl_DStringAppend(&ds, str, TCL_INDEX_NONE); src = Tcl_DStringValue(&ds); field[0] = src; for (i = 0; *src != '\0'; src++) { if (!(*src & 0x80) && Tcl_UniCharIsUpper(UCHAR(*src))) { *src = (char) Tcl_UniCharToLower(UCHAR(*src)); } if (*src == '-') { i++; if (i == XLFD_NUMFIELDS) { continue; } *src = '\0'; field[i] = src + 1; if (i > XLFD_NUMFIELDS) { break; } } } /* * An XLFD of the form -adobe-times-medium-r-*-12-*-* is pretty common, * but it is (strictly) malformed, because the first * is eliding both the * Setwidth and the Addstyle fields. If the Addstyle field is a number, * then assume the above incorrect form was used and shift all the rest of * the fields right by one, so the number gets interpreted as a pixelsize. * This fix is so that we don't get a million reports that "it works under * X (as a native font name), but gives a syntax error under Windows (as a * parsed set of attributes)". */ if ((i > XLFD_ADD_STYLE) && FieldSpecified(field[XLFD_ADD_STYLE])) { if (atoi(field[XLFD_ADD_STYLE]) != 0) { for (j = XLFD_NUMFIELDS - 1; j >= XLFD_ADD_STYLE; j--) { field[j + 1] = field[j]; } field[XLFD_ADD_STYLE] = NULL; i++; } } /* * Bail if we don't have enough of the fields (up to pointsize). */ if (i < XLFD_FAMILY) { Tcl_DStringFree(&ds); return TCL_ERROR; } if (FieldSpecified(field[XLFD_FOUNDRY])) { xaPtr->foundry = Tk_GetUid(field[XLFD_FOUNDRY]); } if (FieldSpecified(field[XLFD_FAMILY])) { faPtr->family = Tk_GetUid(field[XLFD_FAMILY]); } if (FieldSpecified(field[XLFD_WEIGHT])) { faPtr->weight = TkFindStateNum(NULL, NULL, xlfdWeightMap, field[XLFD_WEIGHT]); } if (FieldSpecified(field[XLFD_SLANT])) { xaPtr->slant = TkFindStateNum(NULL, NULL, xlfdSlantMap, field[XLFD_SLANT]); if (xaPtr->slant == TK_FS_ROMAN) { faPtr->slant = TK_FS_ROMAN; } else { faPtr->slant = TK_FS_ITALIC; } } if (FieldSpecified(field[XLFD_SETWIDTH])) { xaPtr->setwidth = TkFindStateNum(NULL, NULL, xlfdSetwidthMap, field[XLFD_SETWIDTH]); } /* XLFD_ADD_STYLE ignored. */ /* * Pointsize in tenths of a point, but treat it as tenths of a pixel for * historical compatibility. */ faPtr->size = 12.0; if (FieldSpecified(field[XLFD_POINT_SIZE])) { if (field[XLFD_POINT_SIZE][0] == '[') { /* * Some X fonts have the point size specified as follows: * * [ N1 N2 N3 N4 ] * * where N1 is the point size (in points, not decipoints!), and * N2, N3, and N4 are some additional numbers that I don't know * the purpose of, so I ignore them. */ faPtr->size = atof(field[XLFD_POINT_SIZE] + 1); } else if (Tcl_GetInt(NULL, field[XLFD_POINT_SIZE], &i) == TCL_OK) { faPtr->size = i/10.0; } else { return TCL_ERROR; } } /* * Pixel height of font. If specified, overrides pointsize. */ if (FieldSpecified(field[XLFD_PIXEL_SIZE])) { if (field[XLFD_PIXEL_SIZE][0] == '[') { /* * Some X fonts have the pixel size specified as follows: * * [ N1 N2 N3 N4 ] * * where N1 is the pixel size, and where N2, N3, and N4 are some * additional numbers that I don't know the purpose of, so I * ignore them. */ faPtr->size = atof(field[XLFD_PIXEL_SIZE] + 1); } else if (Tcl_GetInt(NULL, field[XLFD_PIXEL_SIZE], &i) == TCL_OK) { faPtr->size = (double)i; } else { return TCL_ERROR; } } faPtr->size = -faPtr->size; /* XLFD_RESOLUTION_X ignored. */ /* XLFD_RESOLUTION_Y ignored. */ /* XLFD_SPACING ignored. */ /* XLFD_AVERAGE_WIDTH ignored. */ if (FieldSpecified(field[XLFD_CHARSET])) { xaPtr->charset = Tk_GetUid(field[XLFD_CHARSET]); } else { xaPtr->charset = Tk_GetUid("iso8859-1"); } Tcl_DStringFree(&ds); return TCL_OK; } /* *--------------------------------------------------------------------------- * * FieldSpecified -- * * Helper function for TkParseXLFD(). Determines if a field in the XLFD * was set to a non-null, non-don't-care value. * * Results: * The return value is 0 if the field in the XLFD was not set and should * be ignored, non-zero otherwise. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static int FieldSpecified( const char *field) /* The field of the XLFD to check. Strictly * speaking, only when the string is "*" does * it mean don't-care. However, an unspecified * or question mark is also interpreted as * don't-care. */ { char ch; if (field == NULL) { return 0; } ch = field[0]; return (ch != '*' && ch != '?'); } /* *--------------------------------------------------------------------------- * * TkFontGetPixels -- * * Given a font size specification (as described in the TkFontAttributes * structure) return the number of pixels it represents. * * Results: * As above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ double TkFontGetPixels( Tk_Window tkwin, /* For point->pixel conversion factor. */ double size) /* Font size. */ { double d; if (size <= 0.0) { return -size; } d = size * 25.4 / 72.0; d *= WidthOfScreen(Tk_Screen(tkwin)); d /= WidthMMOfScreen(Tk_Screen(tkwin)); return d; } /* *--------------------------------------------------------------------------- * * TkFontGetPoints -- * * Given a font size specification (as described in the TkFontAttributes * structure) return the number of points it represents. * * Results: * As above. * * Side effects: * None. * *--------------------------------------------------------------------------- */ double TkFontGetPoints( Tk_Window tkwin, /* For pixel->point conversion factor. */ double size) /* Font size. */ { double d; if (size >= 0.0) { return size; } d = -size * 72.0 / 25.4; d *= WidthMMOfScreen(Tk_Screen(tkwin)); d /= WidthOfScreen(Tk_Screen(tkwin)); return d; } /* *------------------------------------------------------------------------- * * TkFontGetAliasList -- * * Given a font name, find the list of all aliases for that font name. * One of the names in this list will probably be the name that this * platform expects when asking for the font. * * Results: * As above. The return value is NULL if the font name has no aliases. * * Side effects: * None. * *------------------------------------------------------------------------- */ const char *const * TkFontGetAliasList( const char *faceName) /* Font name to test for aliases. */ { int i, j; for (i = 0; fontAliases[i] != NULL; i++) { for (j = 0; fontAliases[i][j] != NULL; j++) { if (strcasecmp(faceName, fontAliases[i][j]) == 0) { return fontAliases[i]; } } } return NULL; } /* *------------------------------------------------------------------------- * * TkFontGetFallbacks -- * * Get the list of font fallbacks that the platform-specific code can use * to try to find the closest matching font the name requested. * * Results: * As above. * * Side effects: * None. * *------------------------------------------------------------------------- */ const char *const *const * TkFontGetFallbacks(void) { return fontFallbacks; } /* *------------------------------------------------------------------------- * * TkFontGetGlobalClass -- * * Get the list of fonts to try if the requested font name does not * exist and no fallbacks for that font name could be used either. * The names in this list are considered preferred over all the other * font names in the system when looking for a last-ditch fallback. * * Results: * As above. * * Side effects: * None. * *------------------------------------------------------------------------- */ const char *const * TkFontGetGlobalClass(void) { return globalFontClass; } /* *------------------------------------------------------------------------- * * TkFontGetSymbolClass -- * * Get the list of fonts that are symbolic; used if the operating system * cannot apriori identify symbolic fonts on its own. * * Results: * As above. * * Side effects: * None. * *------------------------------------------------------------------------- */ const char *const * TkFontGetSymbolClass(void) { return symbolClass; } /* *---------------------------------------------------------------------- * * TkDebugFont -- * * This function returns debugging information about a font. * * Results: * The return value is a list with one sublist for each TkFont * corresponding to "name". Each sublist has two elements that contain * the resourceRefCount and objRefCount fields from the TkFont structure. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_Obj * TkDebugFont( Tk_Window tkwin, /* The window in which the font will be used * (not currently used). */ const char *name) /* Name of the desired color. */ { TkFont *fontPtr; Tcl_HashEntry *hashPtr; Tcl_Obj *resultPtr, *objPtr; resultPtr = Tcl_NewObj(); hashPtr = Tcl_FindHashEntry( &((TkWindow *) tkwin)->mainPtr->fontInfoPtr->fontCache, name); if (hashPtr != NULL) { fontPtr = (TkFont *)Tcl_GetHashValue(hashPtr); if (fontPtr == NULL) { Tcl_Panic("TkDebugFont found empty hash table entry"); } for ( ; (fontPtr != NULL); fontPtr = fontPtr->nextPtr) { objPtr = Tcl_NewObj(); Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewWideIntObj(fontPtr->resourceRefCount)); Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewWideIntObj(fontPtr->objRefCount)); Tcl_ListObjAppendElement(NULL, resultPtr, objPtr); } } return resultPtr; } /* *---------------------------------------------------------------------- * * TkFontGetFirstTextLayout -- * * This function returns the first chunk of a Tk_TextLayout, i.e. until * the first font change on the first line (or the whole first line if * there is no such font change). * * Results: * The return value is the byte length of the chunk, the chunk itself is * copied into dst and its Tk_Font into font. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkFontGetFirstTextLayout( Tk_TextLayout layout, /* Layout information, from a previous call to * Tk_ComputeTextLayout(). */ Tk_Font *font, char *dst) { TextLayout *layoutPtr = (TextLayout *) layout; LayoutChunk *chunkPtr; Tcl_Size numBytesInChunk; if ((layoutPtr == NULL) || (layoutPtr->numChunks == 0) || (layoutPtr->chunks->numDisplayChars <= 0)) { dst[0] = '\0'; return 0; } chunkPtr = layoutPtr->chunks; numBytesInChunk = chunkPtr->numBytes; strncpy(dst, chunkPtr->start, numBytesInChunk); *font = layoutPtr->tkfont; return numBytesInChunk; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */