// Font.cc // Copyright (c) 2002 - 2005 Henrik Kinnunen (fluxgen at fluxbox dot org) // // 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 or substantial portions of the Software. // // 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 OR COPYRIGHT HOLDERS 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. //$Id$ #include "StringUtil.hh" #include "stringstream.hh" #include "Font.hh" #include "FontImp.hh" #include "I18n.hh" #include "App.hh" #ifdef HAVE_CONFIG_H #include "config.h" #endif // HAVE_CONFIG_H // for antialias #ifdef USE_XFT #include "XftFontImp.hh" #endif // USE_XFT // for multibyte support #ifdef USE_XMB #include "XmbFontImp.hh" #endif //USE_XMB // standard font system #include "XFontImp.hh" #include "GContext.hh" //use gnu extensions #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif //_GNU_SOURCE #ifndef __USE_GNU #define __USE_GNU #endif //__USE_GNU #include #ifdef HAVE_CSTRING #include #else #include #endif #ifdef HAVE_CSTDLIB #include #else #include #endif #include #include #include #include #ifdef HAVE_CSTDLIB #include #else #include #endif using namespace std; namespace { #ifdef HAVE_SETLOCALE #include #endif //HAVE_SETLOCALE #ifdef HAVE_ICONV /** Recodes the text from one encoding to another assuming cd is correct @param cd the iconv type @param msg text to be converted @param len number of chars to convert @return the recoded string, or 0 on failure */ char* recode(iconv_t cd, const char *msg, size_t size) { // If empty message, yes this can happen, return if(strlen(msg) == 0 || size == 0) return 0; if(strlen(msg) < size) size = strlen(msg); size_t inbytesleft = size; size_t outbytesleft = 4*inbytesleft; char *new_msg = new char[outbytesleft]; char *new_msg_ptr = new_msg; char *msg_ptr = strdup(msg); char *orig_msg_ptr = msg_ptr; // msg_ptr modified in iconv call size_t result = (size_t)(-1); #ifdef HAVE_CONST_ICONV result = iconv(cd, (const char**)(&msg_ptr), &inbytesleft, &new_msg, &outbytesleft); #else result = iconv(cd, &msg_ptr, &inbytesleft, &new_msg, &outbytesleft); #endif // HAVE_CONST_ICONV if (result == (size_t)(-1)) { // iconv can fail for three reasons // 1) Invalid multibyte sequence is encountered in the input // 2) An incomplete multibyte sequence // 3) The output buffer has no more room for the next converted character. // So we the delete new message and return original message delete[] new_msg_ptr; free(orig_msg_ptr); return 0; } free(orig_msg_ptr); *new_msg = '\0'; if(inbytesleft != 0) { delete[] new_msg_ptr; return 0; } return new_msg_ptr; } #else char *recode(int cd, const char *msg, size_t size) { return 0; } #endif // HAVE_ICONV // use to map || => typedef std::map StringMap; typedef StringMap::iterator StringMapIt; StringMap lookup_map; // stores FontCache; typedef FontCache::iterator FontCacheIt; FontCache font_cache; void resetEffects(FbTk::Font* font) { font->setHalo(false); font->setHaloColor(FbTk::Color("white", DefaultScreen(FbTk::App::instance()->display()))); font->setShadow(false); font->setShadowColor(FbTk::Color("black", DefaultScreen(FbTk::App::instance()->display()))); font->setShadowOffY(2); font->setShadowOffX(2); } }; // end nameless namespace namespace FbTk { bool Font::s_multibyte = false; bool Font::s_utf8mode = false; void Font::init() { // must be set before the first XFontSet is created setlocale(LC_CTYPE, ""); } void Font::shutdown() { FontCacheIt fit; for (fit = font_cache.begin(); fit != font_cache.end(); fit++) { FontImp* font = fit->second; if (font) { FontCacheIt it; for (it = fit; it != font_cache.end(); it++) if (it->second == font) it->second = 0; delete font; } } } Font::Font(const char *name): m_fontimp(0), m_rotated(false), m_shadow(false), m_shadow_color("black", DefaultScreen(App::instance()->display())), m_shadow_offx(2), m_shadow_offy(2), m_halo(false), m_halo_color("white", DefaultScreen(App::instance()->display())), #ifdef HAVE_ICONV m_iconv((iconv_t)(-1)) #else m_iconv(-1) #endif // HAVE_ICONV { // MB_CUR_MAX returns the size of a char in the current locale if (MB_CUR_MAX > 1) // more than one byte, then we're multibyte s_multibyte = true; // check for utf-8 mode #ifdef CODESET char *locale_codeset = nl_langinfo(CODESET); #else // openbsd doesnt have this (yet?) char *locale_codeset = 0; #endif // CODESET if (locale_codeset && strcmp("UTF-8", locale_codeset) == 0) { s_utf8mode = true; } else if (locale_codeset != 0) { // if locale isn't UTF-8 we try to // create a iconv pointer so we can // convert non utf-8 strings to utf-8 #ifdef DEBUG cerr<<"FbTk::Font: check UTF-8 convert for codeset = "<first; m_fontimp = cache_entry->second; lookup_map[name] = m_fontstr; resetEffects(this); return true; } FontImp* tmp_font(0); #ifdef USE_XFT if ((*name_it)[0] != '-') tmp_font = new XftFontImp(0, s_utf8mode); #endif // USE_XFT if (!tmp_font) { #ifdef USE_XMB if (s_multibyte || s_utf8mode) tmp_font = new XmbFontImp(0, s_utf8mode); else // basic font implementation #endif // USE_XMB tmp_font = new XFontImp(); } if (tmp_font && tmp_font->load((*name_it).c_str())) { lookup_map[name] = (*name_it); m_fontimp = tmp_font; font_cache[(*name_it)] = tmp_font; m_fontstr = name; resetEffects(this); return true; } delete tmp_font; } return false; } unsigned int Font::textWidth(const char * const text, unsigned int size) const { #ifdef HAVE_ICONV if (m_fontimp->utf8() && m_iconv != (iconv_t)(-1)) { char* rtext = recode(m_iconv, text, size); if (rtext != 0) size = strlen(rtext); unsigned int r = m_fontimp->textWidth(rtext ? rtext : text, size); if (rtext != 0) delete[] rtext; return r; } #endif // HAVE_ICONV return m_fontimp->textWidth(text, size); } unsigned int Font::height() const { return m_fontimp->height(); } int Font::ascent() const { return m_fontimp->ascent(); } int Font::descent() const { return m_fontimp->descent(); } void Font::drawText(const FbDrawable &w, int screen, GC gc, const char *text, size_t len, int x, int y, bool rotate) const { if (text == 0 || len == 0) return; char* rtext = 0; // so we don't end up in a loop with m_shadow static bool first_run = true; #ifdef HAVE_ICONV if (m_fontimp->utf8() && m_iconv != (iconv_t)(-1) && first_run) { rtext = recode(m_iconv, text, len); if (rtext != 0) { len = strlen(rtext); // ok, we can't use utf8 mode since the string is invalid } } #endif // HAVE_ICONV const char *real_text = rtext ? rtext : text; // draw "effects" first if (first_run) { if (m_shadow) { FbTk::GContext shadow_gc(w); shadow_gc.setForeground(m_shadow_color); first_run = false; drawText(w, screen, shadow_gc.gc(), real_text, len, x + m_shadow_offx, y + m_shadow_offy, rotate); first_run = true; } else if (m_halo) { FbTk::GContext halo_gc(w); halo_gc.setForeground(m_halo_color); first_run = false; drawText(w, screen, halo_gc.gc(), real_text, len, x + 1, y + 1, rotate); drawText(w, screen, halo_gc.gc(), real_text, len, x - 1, y + 1, rotate); drawText(w, screen, halo_gc.gc(), real_text, len, x - 1, y - 1, rotate); drawText(w, screen, halo_gc.gc(), real_text, len, x + 1, y - 1, rotate); first_run = true; } } if (!rotate && isRotated()) { // if this was called with request to not rotated the text // we just forward it to the implementation that handles rotation // currently just XFontImp // Using dynamic_cast just temporarly until there's a better solution // to put in FontImp try { XFontImp *font = dynamic_cast(m_fontimp); font->setRotate(false); // disable rotation temporarly font->drawText(w, screen, gc, real_text, len, x, y); font->setRotate(true); // enable rotation } catch (std::bad_cast &bc) { // draw normal... m_fontimp->drawText(w, screen, gc, real_text, len, x, y); } } else m_fontimp->drawText(w, screen, gc, real_text, len, x, y); if (rtext != 0) delete[] rtext; } void Font::rotate(float angle) { /* TODO: reimplement rotated text #ifdef USE_XFT // if we are rotated and we are changing to horiz text // and we were antialiased before we rotated then change to XftFontImp if (isRotated() && angle == 0 && !m_xftfontstr.empty()) m_fontimp.reset(new XftFontImp(m_fontstr.c_str(),s_utf8mode)); #endif // USE_XFT // change to a font imp that handles rotated fonts (i.e just XFontImp at the moment) // if we're going to rotate this font if (angle != 0 && !isRotated()) { m_fontimp.reset(new XFontImp(m_fontstr.c_str())); if (!m_fontimp->loaded()) // if it failed to load font, try default font fixed m_fontimp->load("fixed"); } //Note: only XFontImp implements FontImp::rotate m_fontimp->rotate(angle); m_rotated = (angle == 0 ? false : true); m_angle = angle; */ } };