// Font.cc // Copyright (c) 2002-2004 Henrik Kinnunen (fluxgen at users.sourceforge.net) // // 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 "Font.hh" #include "FontImp.hh" #include "I18n.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 <iostream> #ifdef HAVE_CSTRING #include <cstring> #else #include <string.h> #endif #ifdef HAVE_CSTDLIB #include <cstdlib> #else #include <stdlib.h> #endif #include <list> #include <typeinfo> #include <langinfo.h> #ifdef HAVE_SSTREAM #include <sstream> #define FB_istringstream istringstream #elif HAVE_STRSTREAM #include <strstream> #define FB_istringstream istrstream #else #error "You dont have sstream or strstream headers!" #endif // HAVE_STRSTREAM #ifdef HAVE_CSTDLIB #include <cstdlib> #else #include <stdlib.h> #endif using namespace std; namespace { #ifdef HAVE_SETLOCALE #include <locale.h> #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 int extract_halo_options(const std::string& opts, std::string& color) { std::list< std::string > tokens; size_t sep= opts.find_first_of(':'); if ( sep == std::string::npos ) return 1; FbTk::StringUtil::stringtok(tokens, opts.substr(sep + 1, opts.length()), ";"); tokens.unique(); std::list< std::string >::const_iterator token; for ( token= tokens.begin(); token != tokens.end(); token++ ) { if ( (*token).find("color=", 0) != std::string::npos ) { size_t s= (*token).find_first_of('='); std::string c= (*token).substr(s + 1, (*token).length()); if ( !c.empty() ) std::swap(color, c); } } return 1; } int extract_shadow_options(const std::string& opts, std::string& color, int& offx, int& offy) { std::list< std::string > tokens; size_t sep= opts.find_first_of(':'); if ( sep == std::string::npos ) return 1; FbTk::StringUtil::stringtok(tokens, opts.substr(sep + 1, opts.length()), ";"); tokens.unique(); std::list< std::string >::const_iterator token; for ( token= tokens.begin(); token != tokens.end(); token++ ) { if ( (*token).find("color=", 0) != std::string::npos ) { size_t s= (*token).find_first_of('='); std::string c= (*token).substr(s + 1, (*token).length()); if ( !c.empty() ) std::swap(color, c); } else if ( (*token).find("offsetx=", 0) != std::string::npos ) { size_t s= (*token).find_first_of('='); FB_istringstream o((*token).substr(s + 1, (*token).length())); if ( !o.eof() ) { o >> offx; } } else if ( (*token).find("offsety=", 0) != std::string::npos ) { size_t s= (*token).find_first_of('='); FB_istringstream o((*token).substr(s + 1, (*token).length())); if ( !o.eof() ) { o >> offy; } } } return 1; }; }; // end nameless namespace namespace FbTk { bool Font::m_multibyte = false; bool Font::m_utf8mode = false; // some initialisation for using fonts void fontInit() { setlocale(LC_CTYPE, ""); } Font::Font(const char *name, bool antialias): m_fontimp(0), m_antialias(false), m_rotated(false), m_shadow(false), m_shadow_color("#000000"), m_shadow_offx(1), m_shadow_offy(1), m_halo(false), m_halo_color("#ffffff"), #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 m_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) { m_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 = "<<locale_codeset<<endl; #endif // DEBUG #ifdef HAVE_ICONV m_iconv = iconv_open("UTF-8", locale_codeset); if(m_iconv == (iconv_t)(-1)) { cerr<<"FbTk::Font: code error: from "<<locale_codeset<<" to: UTF-8"<<endl; // if we failed with iconv then we can't convert // the strings to utf-8, so we disable utf8 mode m_utf8mode = false; } else { // success, we can now enable utf8mode // and if antialias is on later we can recode // the non utf-8 string to utf-8 and use utf-8 // drawing functions m_utf8mode = true; } #endif // HAVE_ICONV } #ifdef DEBUG cerr<<"FbTk::Font m_iconv = "<<(int)m_iconv<<endl; #endif // DEBUG // create the right font implementation // antialias is prio 1 #ifdef USE_XFT if (antialias) { m_fontimp.reset(new XftFontImp(0, m_utf8mode)); } #endif //USE_XFT // if we didn't create a Xft font then create basic font if (m_fontimp.get() == 0) { #ifdef USE_XMB if (m_multibyte || m_utf8mode) m_fontimp.reset(new XmbFontImp(0, m_utf8mode)); else // basic font implementation #endif // USE_XMB m_fontimp.reset(new XFontImp()); } if (name != 0) { load(name); } } Font::~Font() { #ifdef HAVE_ICONV if (m_iconv != (iconv_t)(-1)) iconv_close(m_iconv); #endif // HAVE_ICONV } void Font::setAntialias(bool flag) { bool loaded = m_fontimp->loaded(); #ifdef USE_XFT if (flag && !isAntialias() && !m_rotated) { m_fontimp.reset(new XftFontImp(m_fontstr.c_str(), m_utf8mode)); } else if (!flag && isAntialias()) #endif // USE_XFT { #ifdef USE_XMB if (m_multibyte || m_utf8mode) m_fontimp.reset(new XmbFontImp(m_fontstr.c_str(), m_utf8mode)); else #endif // USE_XMB m_fontimp.reset(new XFontImp(m_fontstr.c_str())); } if (m_fontimp->loaded() != loaded) { // if the new font failed to load, fall back to 'fixed' if (!m_fontimp->load("fixed")) {// if that failes too, output warning _FB_USES_NLS; cerr<<_FBTKTEXT(Error, CantFallbackFont, "Warning: can't load fallback font", "Attempt to load the last-resort default font failed")<<" 'fixed'."<<endl; } } m_antialias = flag; } bool Font::load(const std::string &name) { if (name.size() == 0) return false; // default values for font options m_shadow = false; m_halo = false; // everything after ':' is a fontoption // -> extract 'halo' and 'shadow' and // load remaining fname size_t sep= name.find_first_of(':'); if ( sep != std::string::npos ) { std::list< std::string > tokens; std::string fname; fname= std::string(name.c_str(), sep); FbTk::StringUtil::stringtok(tokens, name.substr(sep + 1), ","); tokens.unique(); bool firstone= true; std::list< std::string >::const_iterator token; // check tokens and extract extraoptions for halo and shadow for( token= tokens.begin(); token != tokens.end(); token++ ) { if ( (*token).find("halo",0) != std::string::npos ) { m_halo= true; extract_halo_options(*token, m_halo_color); } else if ( (*token).find("shadow", 0) != std::string::npos ) { m_shadow= true; extract_shadow_options(*token, m_shadow_color, m_shadow_offx, m_shadow_offy); } else { if ( !firstone ) fname+= ", "; else firstone= false; fname= fname + ":" + *token; } } m_fontstr = fname; } else m_fontstr = name; return m_fontimp->load(m_fontstr.c_str()); } 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(FbTk::Color(m_shadow_color.c_str(), screen)); 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(FbTk::Color(m_halo_color.c_str(), screen)); 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<XFontImp *>(m_fontimp.get()); 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) { #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 && isAntialias()) m_fontimp.reset(new XftFontImp(m_fontstr.c_str(), m_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 && isAntialias() && !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; } };