/* * Copyright (C) 1997-2000, Michael Jennings * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies of the Software, its documentation and marketing & publicity * materials, and acknowledgment shall be given in the documentation, materials * and software packages that this Software was used. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ static const char cvs_ident[] = "$Id$"; #include "config.h" #include "feature.h" #include #include #include #include #include #include "command.h" #include "font.h" #include "startup.h" #include "options.h" #include "screen.h" #include "term.h" #include "windows.h" char **etfonts = NULL; unsigned char font_idx = DEF_FONT_IDX, def_font_idx = DEF_FONT_IDX, font_cnt = 0; char *rs_font[NFONTS]; #ifdef MULTI_CHARSET char *rs_mfont[NFONTS]; char **etmfonts = NULL; const char *def_mfontName[] = {MFONT0, MFONT1, MFONT2, MFONT3, MFONT4}; #endif const char *def_fontName[] = {FONT0, FONT1, FONT2, FONT3, FONT4}; unsigned char font_chg = 0; fontshadow_t fshadow = { { 0, 0, 0, 0 }, { 0, 0, 0, 1 }, 1 }; static cachefont_t *font_cache = NULL, *cur_font = NULL; static void font_cache_add(const char *name, unsigned char type, void *info); static void font_cache_del(const void *info); static cachefont_t *font_cache_find(const char *name, unsigned char type); static void *font_cache_find_info(const char *name, unsigned char type); static unsigned char get_corner(const char *corner); void eterm_font_add(char ***plist, const char *fontname, unsigned char idx) { char **flist; D_FONT(("Adding \"%s\" at %u (%8p)\n", NONULL(fontname), (unsigned int) idx, plist)); ASSERT(plist != NULL); flist = *plist; if (idx >= font_cnt) { unsigned char new_size = sizeof(char *) * (idx + 1); if (etfonts) { etfonts = (char **) REALLOC(etfonts, new_size); #ifdef MULTI_CHARSET etmfonts = (char **) REALLOC(etmfonts, new_size); D_FONT((" -> Reallocated font lists: %u bytes at %8p/%8p\n", new_size, etfonts, etmfonts)); #else D_FONT((" -> Reallocated font list: %u bytes at %8p\n", new_size, etfonts)); #endif } else { etfonts = (char **) MALLOC(new_size); #ifdef MULTI_CHARSET etmfonts = (char **) MALLOC(new_size); D_FONT((" -> Allocated font lists: %u bytes at %8p/%8p\n", new_size, etfonts, etmfonts)); #else D_FONT((" -> Allocating font list: %u bytes at %8p\n", new_size, etfonts)); #endif } MEMSET(etfonts + font_cnt, 0, sizeof(char *) * (idx - font_cnt + 1)); #ifdef MULTI_CHARSET MEMSET(etmfonts + font_cnt, 0, sizeof(char *) * (idx - font_cnt + 1)); #endif font_cnt = idx + 1; #ifdef MULTI_CHARSET flist = ((plist == &etfonts) ? (etfonts) : (etmfonts)); #else flist = etfonts; #endif } else { if (flist[idx]) { if ((flist[idx] == fontname) || (!strcasecmp(flist[idx], fontname))) { return; } FREE(flist[idx]); } } flist[idx] = STRDUP(fontname); DUMP_FONTS(); } void eterm_font_delete(char **flist, unsigned char idx) { ASSERT(idx < font_cnt); if (flist[idx]) { FREE(flist[idx]); } flist[idx] = NULL; } static void font_cache_add(const char *name, unsigned char type, void *info) { cachefont_t *font; D_FONT(("font_cache_add(%s, %d, %8p) called.\n", NONULL(name), type, info)); font = (cachefont_t *) MALLOC(sizeof(cachefont_t)); font->name = STRDUP(name); font->type = type; font->ref_cnt = 1; switch (type) { case FONT_TYPE_X: font->fontinfo.xfontinfo = (XFontStruct *) info; break; case FONT_TYPE_TTF: break; case FONT_TYPE_FNLIB: break; default: break; } D_FONT((" -> Created new cachefont_t struct at %p: \"%s\", %d, %p\n", font, font->name, font->type, font->fontinfo.xfontinfo)); if (font_cache == NULL) { font_cache = cur_font = font; font->next = NULL; D_FONT((" -> Stored as first font in cache. font_cache == cur_font == font == %p\n", font_cache)); D_FONT((" -> font_cache->next == cur_font->next == font->next == %p\n", font_cache->next)); } else { D_FONT((" -> font_cache->next == %p, cur_font->next == %p\n", font_cache->next, cur_font->next)); cur_font->next = font; font->next = NULL; cur_font = font; D_FONT((" -> Stored font in cache. font_cache == %p, cur_font == %p\n", font_cache, cur_font)); D_FONT((" -> font_cache->next == %p, cur_font->next == %p\n", font_cache->next, cur_font->next)); } } static void font_cache_del(const void *info) { cachefont_t *current, *tmp; D_FONT(("font_cache_del(%8p) called.\n", info)); if (font_cache == NULL) { return; } if (((font_cache->type == FONT_TYPE_X) && (font_cache->fontinfo.xfontinfo == (XFontStruct *) info))) { D_FONT((" -> Match found at font_cache (%8p). Font name is \"%s\"\n", font_cache, NONULL(font_cache->name))); if (--(font_cache->ref_cnt) == 0) { D_FONT((" -> Reference count is now 0. Deleting from cache.\n")); current = font_cache; font_cache = current->next; XFreeFont(Xdisplay, (XFontStruct *) info); FREE(current->name); FREE(current); } else { D_FONT((" -> Reference count is %d. Returning.\n", font_cache->ref_cnt)); } return; } else if ((font_cache->type == FONT_TYPE_TTF) && (0)) { } else if ((font_cache->type == FONT_TYPE_FNLIB) && (0)) { } else { for (current = font_cache; current->next; current = current->next) { if (((current->next->type == FONT_TYPE_X) && (current->next->fontinfo.xfontinfo == (XFontStruct *) info))) { D_FONT((" -> Match found at current->next (%8p, current == %8p). Font name is \"%s\"\n", current->next, current, NONULL(current->next->name))); if (--(current->next->ref_cnt) == 0) { D_FONT((" -> Reference count is now 0. Deleting from cache.\n")); tmp = current->next; current->next = current->next->next; XFreeFont(Xdisplay, (XFontStruct *) info); if (cur_font == tmp) { cur_font = current; /* If we're nuking the last entry in the cache, point cur_font to the *new* last entry. */ } FREE(tmp->name); FREE(tmp); } else { D_FONT((" -> Reference count is %d. Returning.\n", font_cache->ref_cnt)); } return; } else if ((current->next->type == FONT_TYPE_TTF) && (0)) { } else if ((current->next->type == FONT_TYPE_FNLIB) && (0)) { } } } } static cachefont_t * font_cache_find(const char *name, unsigned char type) { cachefont_t *current; ASSERT_RVAL(name != NULL, NULL); D_FONT(("font_cache_find(%s, %d) called.\n", NONULL(name), type)); for (current = font_cache; current; current = current->next) { D_FONT((" -> Checking current (%8p), type == %d, name == %s\n", current, current->type, NONULL(current->name))); if ((current->type == type) && !strcasecmp(current->name, name)) { D_FONT((" -> Match!\n")); return (current); } } D_FONT(("No matches found. =(\n")); return ((cachefont_t *) NULL); } static void * font_cache_find_info(const char *name, unsigned char type) { cachefont_t *current; REQUIRE_RVAL(name != NULL, NULL); D_FONT(("font_cache_find_info(%s, %d) called.\n", NONULL(name), type)); for (current = font_cache; current; current = current->next) { D_FONT((" -> Checking current (%8p), type == %d, name == %s\n", current, current->type, NONULL(current->name))); if ((current->type == type) && !strcasecmp(current->name, name)) { D_FONT((" -> Match!\n")); switch (type) { case FONT_TYPE_X: return ((void *) current->fontinfo.xfontinfo); break; case FONT_TYPE_TTF: return (NULL); break; case FONT_TYPE_FNLIB: return (NULL); break; default: return (NULL); break; } } } D_FONT(("No matches found. =(\n")); return (NULL); } void * load_font(const char *name, const char *fallback, unsigned char type) { cachefont_t *font; XFontStruct *xfont; D_FONT(("load_font(%s, %s, %d) called.\n", NONULL(name), NONULL(fallback), type)); if (type == 0) { type = FONT_TYPE_X; } if (name == NULL) { if (fallback) { name = fallback; fallback = "fixed"; } else { name = "fixed"; #ifdef MULTI_CHARSET fallback = "-misc-fixed-medium-r-normal--13-120-75-75-c-60-iso10646-1"; #else fallback = "-misc-fixed-medium-r-normal--13-120-75-75-c-60-iso8859-1"; #endif } } else if (fallback == NULL) { fallback = "fixed"; } D_FONT((" -> Using name == \"%s\" and fallback == \"%s\"\n", name, fallback)); if ((font = font_cache_find(name, type)) != NULL) { font_cache_add_ref(font); D_FONT((" -> Font found in cache. Incrementing reference count to %d and returning existing data.\n", font->ref_cnt)); switch (type) { case FONT_TYPE_X: return ((void *) font->fontinfo.xfontinfo); break; case FONT_TYPE_TTF: return (NULL); break; case FONT_TYPE_FNLIB: return (NULL); break; default: return (NULL); break; } } if (type == FONT_TYPE_X) { if ((xfont = XLoadQueryFont(Xdisplay, name)) == NULL) { print_error("Unable to load font \"%s\". Falling back on \"%s\"\n", name, fallback); if ((xfont = XLoadQueryFont(Xdisplay, fallback)) == NULL) { fatal_error("Couldn't load the fallback font either. Giving up.\n"); } else { font_cache_add(fallback, type, (void *) xfont); } } else { font_cache_add(name, type, (void *) xfont); } return ((void *) xfont); } else if (type == FONT_TYPE_TTF) { return (NULL); } else if (type == FONT_TYPE_FNLIB) { return (NULL); } ASSERT_NOTREACHED_RVAL(NULL); } void free_font(const void *info) { ASSERT(info != NULL); font_cache_del(info); } void change_font(int init, const char *fontname) { #ifndef NO_BOLDFONT static XFontStruct *boldFont = NULL; #endif short idx = 0, old_idx = font_idx; int fh, fw = 0; D_FONT(("change_font(%d, \"%s\"): def_font_idx == %u, font_idx == %u\n", init, NONULL(fontname), (unsigned int) def_font_idx, (unsigned int) font_idx)); if (init) { font_idx = def_font_idx; ASSERT(etfonts != NULL); ASSERT(etfonts[font_idx] != NULL); #ifdef MULTI_CHARSET ASSERT(etmfonts != NULL); ASSERT(etmfonts[font_idx] != NULL); #endif } else { ASSERT(fontname != NULL); switch (*fontname) { case '\0': font_idx = def_font_idx; fontname = NULL; break; /* special (internal) prefix for font commands */ case FONT_CMD: idx = atoi(++fontname); switch (*fontname) { case '+': NEXT_FONT(idx); break; case '-': PREV_FONT(idx); break; default: if (*fontname != '\0' && !isdigit(*fontname)) return; BOUND(idx, 0, (font_cnt - 1)); font_idx = idx; break; } fontname = NULL; break; default: for (idx = 0; idx < font_cnt; idx++) { if (!strcasecmp(etfonts[idx], fontname)) { font_idx = idx; fontname = NULL; break; } } break; } if (fontname != NULL) { eterm_font_add(&etfonts, fontname, font_idx); } else if (font_idx == old_idx) { D_FONT((" -> Change to the same font index (%d) we had before? I don't think so.\n", font_idx)); return; } } D_FONT((" -> Changing to font index %u (\"%s\")\n", (unsigned int) font_idx, NONULL(etfonts[font_idx]))); if (TermWin.font) { if (font_cache_find_info(etfonts[font_idx], FONT_TYPE_X) != TermWin.font) { free_font(TermWin.font); TermWin.font = load_font(etfonts[font_idx], "fixed", FONT_TYPE_X); } } else { TermWin.font = load_font(etfonts[font_idx], "fixed", FONT_TYPE_X); } #ifndef NO_BOLDFONT if (init && rs_boldFont != NULL) { boldFont = load_font(rs_boldFont, "-misc-fixed-bold-r-semicondensed--13-120-75-75-c-60-iso8859-1", FONT_TYPE_X); } #endif #ifdef MULTI_CHARSET if (TermWin.mfont) { if (font_cache_find_info(etmfonts[font_idx], FONT_TYPE_X) != TermWin.mfont) { free_font(TermWin.mfont); TermWin.mfont = load_font(etmfonts[font_idx], "k14", FONT_TYPE_X); } } else { TermWin.mfont = load_font(etmfonts[font_idx], "k14", FONT_TYPE_X); } # ifdef USE_XIM if (xim_input_context) { if (TermWin.fontset) { XFreeFontSet(Xdisplay, TermWin.fontset); } TermWin.fontset = create_fontset(etfonts[font_idx], etmfonts[font_idx]); xim_set_fontset(); } # endif #endif /* MULTI_CHARSET */ if (!init) { XSetFont(Xdisplay, TermWin.gc, TermWin.font->fid); } fw = TermWin.font->min_bounds.width; #ifdef MULTI_CHARSET fh = (MAX((encoding_method == LATIN1 ? 0 : TermWin.mfont->ascent), TermWin.font->ascent) + MAX((encoding_method == LATIN1 ? 0 : TermWin.mfont->descent), TermWin.font->descent) + rs_line_space); #else fh = TermWin.font->ascent + TermWin.font->descent + rs_line_space; #endif D_FONT(("Font information: Ascent == %hd, Descent == %hd, width min/max %d/%d\n", TermWin.font->ascent, TermWin.font->descent, TermWin.font->min_bounds.width, TermWin.font->max_bounds.width)); if (TermWin.font->min_bounds.width == TermWin.font->max_bounds.width) TermWin.fprop = 0; /* Mono-spaced (fixed width) font */ else TermWin.fprop = 1; /* Proportional font */ if (TermWin.fprop && TermWin.font->per_char && (TermWin.font->max_bounds.width - TermWin.font->min_bounds.width >= 3)) { int cw, n = 0, sum = 0, sumsq = 0, min_w, max_w; unsigned int i; double dev; min_w = fw; max_w = TermWin.font->max_bounds.width; for (i = TermWin.font->min_char_or_byte2; i <= TermWin.font->max_char_or_byte2; i++) { cw = TermWin.font->per_char[i].width; if (cw >= min_w && cw <= max_w) { sum += cw; sumsq += (cw * cw); n++; } } if (n) { dev = sqrt((sumsq - (sum * sum) / n) / n); /* Final font width is the average width plus 2 standard deviations, but no larger than the font's max width */ fw = ((sum / n) + (((int) dev) << 1)); D_FONT(("Proportional font optimizations: Average width %d, standard deviation %3.2f, new width %d\n", (sum / n), dev, fw)); UPPER_BOUND(fw, max_w); } else { LOWER_BOUND(fw, TermWin.font->max_bounds.width); } } else { LOWER_BOUND(fw, TermWin.font->max_bounds.width); } /* not the first time thru and sizes haven't changed */ if (fw == TermWin.fwidth && fh == TermWin.fheight) return; TermWin.fwidth = fw; TermWin.fheight = fh; /* check that size of boldFont is okay */ #ifndef NO_BOLDFONT TermWin.boldFont = NULL; if (boldFont != NULL) { fw = boldFont->min_bounds.width; fh = boldFont->ascent + boldFont->descent + rs_line_space; if (TermWin.fprop == 0) { /* bold font must also be monospaced */ if (fw != boldFont->max_bounds.width) fw = -1; } else { LOWER_BOUND(fw, boldFont->max_bounds.width); } if (fw == TermWin.fwidth && fh == TermWin.fheight) { TermWin.boldFont = boldFont; } } #endif /* NO_BOLDFONT */ set_colorfgbg(); TermWin.width = TermWin.ncol * TermWin.fwidth; TermWin.height = TermWin.nrow * TermWin.fheight; D_FONT((" -> New font width/height = %ldx%ld, making the terminal size %ldx%ld\n", TermWin.fwidth, TermWin.fheight, TermWin.width, TermWin.height)); if (init) { szHint.width_inc = TermWin.fwidth; szHint.height_inc = TermWin.fheight; szHint.min_width = szHint.base_width + szHint.width_inc; szHint.min_height = szHint.base_height + szHint.height_inc; szHint.width = szHint.base_width + TermWin.width; szHint.height = szHint.base_height + TermWin.height; szHint.flags = PMinSize | PResizeInc | PBaseSize; } else { parent_resize(); font_chg++; } return; } static unsigned char get_corner(const char *corner) { if (!BEG_STRCASECMP(corner, "tl ") || !BEG_STRCASECMP(corner, "top_left")) { return SHADOW_TOP_LEFT; } else if (!BEG_STRCASECMP(corner, "tr ") || !BEG_STRCASECMP(corner, "top_right")) { return SHADOW_TOP_RIGHT; } else if (!BEG_STRCASECMP(corner, "bl ") || !BEG_STRCASECMP(corner, "bottom_left")) { return SHADOW_BOTTOM_LEFT; } else if (!BEG_STRCASECMP(corner, "br ") || !BEG_STRCASECMP(corner, "bottom_right")) { return SHADOW_BOTTOM_RIGHT; } else { return 255; } } void set_shadow_color_by_name(unsigned char which, const char *color_name) { Pixel p; ASSERT(which <= 4); p = get_color_by_name(color_name, "#000000"); fshadow.color[which] = p; fshadow.shadow[which] = fshadow.do_shadow = 1; } void set_shadow_color_by_pixel(unsigned char which, Pixel p) { ASSERT(which <= 4); fshadow.color[which] = p; fshadow.shadow[which] = fshadow.do_shadow = 1; } /* Possible syntax for the font effects line: font fx font fx outline font fx shadow font fx emboss font fx carved ^^^^^^^ | \- This part is not included in the contents of the line variable. */ unsigned char parse_font_fx(const char *line) { char *color, *corner; unsigned char which, n; Pixel p; ASSERT(line != NULL); n = num_words(line); if (!BEG_STRCASECMP(line, "none")) { MEMSET(&fshadow, 0, sizeof(fontshadow_t)); } else if (!BEG_STRCASECMP(line, "outline")) { if (n != 2) { return 0; } color = get_word(2, line); p = get_color_by_name(color, "black"); FREE(color); for (which = 0; which < 4; which++) { set_shadow_color_by_pixel(which, p); } } else if (!BEG_STRCASECMP(line, "shadow")) { if (n == 2) { which = SHADOW_BOTTOM_RIGHT; color = get_word(2, line); } else if (n == 3) { color = get_word(3, line); corner = get_pword(2, line); which = get_corner(corner); if (which >= 4) { return 0; } } else { return 0; } set_shadow_color_by_name(which, color); FREE(color); } else if (!BEG_STRCASECMP(line, "emboss")) { if (n != 3) { return 0; } color = get_word(2, line); p = get_color_by_name(color, "black"); set_shadow_color_by_pixel(SHADOW_BOTTOM_RIGHT, p); FREE(color); color = get_word(3, line); p = get_color_by_name(color, "white"); set_shadow_color_by_pixel(SHADOW_TOP_LEFT, p); FREE(color); } else if (!BEG_STRCASECMP(line, "carved")) { if (n != 3) { return 0; } color = get_word(2, line); p = get_color_by_name(color, "black"); set_shadow_color_by_pixel(SHADOW_TOP_LEFT, p); FREE(color); color = get_word(3, line); p = get_color_by_name(color, "white"); set_shadow_color_by_pixel(SHADOW_BOTTOM_RIGHT, p); FREE(color); } else { unsigned char i; for (i = 0; i < 4; i++) { which = get_corner(line); if (which >= 4) { which = i; color = get_word(1, line); line = get_pword(2, line); } else { color = get_word(2, line); line = get_pword(3, line); } set_shadow_color_by_name(which, color); FREE(color); if (line == NULL) { break; } } } return 1; }