From 7b6ab828c7e5453a2720462156d165707935c9ef Mon Sep 17 00:00:00 2001 From: Mathias Gumz Date: Tue, 2 Oct 2012 14:24:47 +0200 Subject: Improved vertical alignment of text in FbTk::TextButton The old formula for vertical align text inside FbTk::TextButton ('height/2 + font_ascent/2 - 1') produced not always good looking results, escpecially when different fonts are involved (eg, ClockTool and WorkspaceName have different fonts and font-sizes). '(height - font_ascent) / 2 - 1' produces better results. Additional changes: * added ASCII-Art to document the involved entities when calculating the baseline * rewritten tests/testFont.cc to accept multiples texts and multiple fonts * removed some internal parts of FbTk::Font from the public interface --- src/FbTk/Font.cc | 75 ++++++++++------- src/FbTk/Font.hh | 17 ++-- src/FbTk/TextButton.cc | 78 ++++++++++++------ src/FbTk/TextButton.hh | 4 +- src/Toolbar.cc | 2 - src/tests/testFont.cc | 212 ++++++++++++++++++++++++------------------------- 6 files changed, 215 insertions(+), 173 deletions(-) diff --git a/src/FbTk/Font.cc b/src/FbTk/Font.cc index 37621a8..544ddc2 100644 --- a/src/FbTk/Font.cc +++ b/src/FbTk/Font.cc @@ -66,6 +66,7 @@ #include #include #include +#include #include @@ -83,12 +84,10 @@ namespace { // use to map || => typedef map StringMap; typedef StringMap::iterator StringMapIt; -StringMap lookup_map; // stores FontCache; typedef FontCache::iterator FontCacheIt; -FontCache font_cache; void resetEffects(FbTk::Font& font) { @@ -100,24 +99,30 @@ void resetEffects(FbTk::Font& font) { font.setShadowOffX(2); } + +StringMap s_lookup_map; +FontCache s_font_cache; +bool s_multibyte = false; // if the fontimp should be a multibyte font +bool s_utf8mode = false; // should the font use utf8 font imp + + } // end nameless namespace namespace FbTk { -bool Font::s_multibyte = false; -bool Font::s_utf8mode = false; +const char Font::DEFAULT_FONT[] = "__DEFAULT__"; void Font::shutdown() { FontCacheIt fit; - for (fit = font_cache.begin(); fit != font_cache.end(); fit++) { + for (fit = s_font_cache.begin(); fit != s_font_cache.end(); ++fit) { FontImp* font = fit->second; if (font) { FontCacheIt it; - for (it = fit; it != font_cache.end(); ++it) + for (it = fit; it != s_font_cache.end(); ++it) if (it->second == font) it->second = 0; delete font; @@ -125,6 +130,15 @@ void Font::shutdown() { } } +bool Font::multibyte() { + return s_multibyte; +} + +bool Font::utf8() { + return s_utf8mode; +} + + Font::Font(const char *name): m_fontimp(0), m_shadow(false), m_shadow_color("black", DefaultScreen(App::instance()->display())), @@ -135,13 +149,15 @@ Font::Font(const char *name): if (MB_CUR_MAX > 1) // more than one byte, then we're multibyte s_multibyte = true; - // check for utf-8 mode -#if defined(CODESET) && !defined(_WIN32) - char *locale_codeset = nl_langinfo(CODESET); -#else // openbsd doesnt have this (yet?) + char *locale_codeset = 0; -#endif // defined(CODESET) && !defined(_WIN32) + // openbsd doesnt have this (yet?) +#if defined(CODESET) && !defined(_WIN32) + locale_codeset = nl_langinfo(CODESET); +#endif + + // check for utf-8 mode if (locale_codeset && strcmp("UTF-8", locale_codeset) == 0) { s_utf8mode = true; } else if (locale_codeset != 0) { @@ -159,15 +175,15 @@ Font::~Font() { bool Font::load(const string &name) { - if (name.size() == 0) + if (name.empty()) return false; StringMapIt lookup_entry; FontCacheIt cache_entry; // check if one of || is already there - if ((lookup_entry = lookup_map.find(name)) != lookup_map.end() && - (cache_entry = font_cache.find(lookup_entry->second)) != font_cache.end()) { + if ((lookup_entry = s_lookup_map.find(name)) != s_lookup_map.end() && + (cache_entry = s_font_cache.find(lookup_entry->second)) != s_font_cache.end()) { m_fontstr = cache_entry->first; m_fontimp = cache_entry->second; resetEffects(*this); @@ -185,10 +201,10 @@ bool Font::load(const string &name) { FbTk::StringUtil::removeTrailingWhitespace(*name_it); FbTk::StringUtil::removeFirstWhitespace(*name_it); - if ((cache_entry = font_cache.find(*name_it)) != font_cache.end()) { + if ((cache_entry = s_font_cache.find(*name_it)) != s_font_cache.end()) { m_fontstr = cache_entry->first; m_fontimp = cache_entry->second; - lookup_map[name] = m_fontstr; + s_lookup_map[name] = m_fontstr; resetEffects(*this); return true; } @@ -210,32 +226,33 @@ bool Font::load(const string &name) { #ifdef USE_XFT if ((*name_it)[0] != '-') { - if (*name_it == "__DEFAULT__") + if (*name_it == Font::DEFAULT_FONT) realname = "monospace"; - tmp_font = new XftFontImp(0, s_utf8mode); + tmp_font = new XftFontImp(0, utf8()); } #endif // USE_XFT if (!tmp_font) { - if (*name_it == "__DEFAULT__") + if (*name_it == Font::DEFAULT_FONT) realname = "fixed"; #ifdef USE_XMB - if (s_multibyte || s_utf8mode) { - tmp_font = new XmbFontImp(0, s_utf8mode); - } else // basic font implementation + if (multibyte() || utf8()) { + tmp_font = new XmbFontImp(0, utf8()); + } #endif // USE_XMB - { - tmp_font = new XFontImp(); - } + } + + if (!tmp_font) { + tmp_font = new XFontImp(); } if (tmp_font && tmp_font->load(realname.c_str())) { - lookup_map[name] = (*name_it); + s_lookup_map[name] = (*name_it); m_fontimp = tmp_font; - font_cache[(*name_it)] = tmp_font; + s_font_cache[(*name_it)] = tmp_font; m_fontstr = name; resetEffects(*this); return true; @@ -251,6 +268,10 @@ unsigned int Font::textWidth(const char* text, unsigned int size) const { return m_fontimp->textWidth(text, size); } +unsigned int Font::textWidth(const BiDiString &text) const { + return textWidth(text.visual().c_str(), text.visual().size()); +} + unsigned int Font::height() const { return m_fontimp->height(); } diff --git a/src/FbTk/Font.hh b/src/FbTk/Font.hh index fb399d9..6ea2571 100644 --- a/src/FbTk/Font.hh +++ b/src/FbTk/Font.hh @@ -43,17 +43,19 @@ class FbDrawable; class Font { public: + static const char DEFAULT_FONT[]; + + /// called at FbTk::App destruction time, cleans up cache static void shutdown(); /// @return true if multibyte is enabled, else false - static bool multibyte() { return s_multibyte; } + static bool multibyte(); /// @return true if utf-8 mode is enabled, else false - static bool utf8() { return s_utf8mode; } - + static bool utf8(); - explicit Font(const char *name = "__DEFAULT__"); + explicit Font(const char* name = DEFAULT_FONT); virtual ~Font(); /** Load a font @@ -76,9 +78,7 @@ public: @return size of text in pixels */ unsigned int textWidth(const char* text, unsigned int size) const; - unsigned int textWidth(const BiDiString &text) const { - return textWidth(text.visual().c_str(), text.visual().size()); - } + unsigned int textWidth(const BiDiString &text) const; unsigned int height() const; int ascent() const; @@ -119,9 +119,6 @@ private: FbTk::FontImp* m_fontimp; ///< font implementation std::string m_fontstr; ///< font name - static bool s_multibyte; ///< if the fontimp should be a multibyte font - static bool s_utf8mode; ///< should the font use utf8 font imp - int m_angle; ///< rotation angle bool m_shadow; ///< shadow text Color m_shadow_color; ///< shadow color diff --git a/src/FbTk/TextButton.cc b/src/FbTk/TextButton.cc index c31e3d6..33a2b44 100644 --- a/src/FbTk/TextButton.cc +++ b/src/FbTk/TextButton.cc @@ -23,6 +23,7 @@ #include "TextUtils.hh" #include "Font.hh" #include "GContext.hh" +#include namespace FbTk { @@ -62,16 +63,18 @@ void TextButton::setJustify(FbTk::Justify just) { } bool TextButton::setOrientation(FbTk::Orientation orient) { + if (orient == m_orientation || !m_font->validOrientation(orient)) return false; + invalidateBackground(); if (((m_orientation == FbTk::ROT0 || m_orientation == FbTk::ROT180) && (orient == FbTk::ROT90 || orient == FbTk::ROT270)) || ((m_orientation == FbTk::ROT90 || m_orientation == FbTk::ROT270) && (orient == FbTk::ROT0 || orient == FbTk::ROT180))) { - // flip width and height + m_orientation = orient; resize(height(), width()); } else { @@ -112,8 +115,7 @@ void TextButton::setTextPadding(unsigned int padding) { /// clear window and redraw text void TextButton::clear() { - TextButton::clearArea(0, 0, - width(), height()); + TextButton::clearArea(0, 0, width(), height()); } void TextButton::clearArea(int x, int y, @@ -135,34 +137,58 @@ void TextButton::renderForeground(FbWindow &win, FbDrawable &drawable) { } void TextButton::drawText(int x_offset, int y_offset, FbDrawable *drawable) { + + if (drawable == 0) + drawable = this; + const FbString& visual = text().visual(); unsigned int textlen = visual.size(); - unsigned int textw = width(); - unsigned int texth = height(); - translateSize(m_orientation, textw, texth); + unsigned int button_width = width(); + unsigned int button_height = height(); + + translateSize(m_orientation, button_width, button_height); - int align_x = FbTk::doAlignment(textw - x_offset - m_left_padding - m_right_padding, + // horizontal alignment, cut off text if needed + int align_x = FbTk::doAlignment(button_width - x_offset - m_left_padding - m_right_padding, bevel(), justify(), font(), visual.data(), visual.size(), textlen); // return new text len - // center text by default - int center_pos = texth/2 + font().ascent()/2 - 1; - - int textx = align_x + x_offset + m_left_padding; - int texty = center_pos + y_offset; - - if (drawable == 0) - drawable = this; + // + // we center the text vertically. to do this we have to nudge the + // baseline a little bit down so the "middle" of the glyph is placed + // on the middle of the button. we "assume", that ascent/2 is roughly + // the middle of the glyph. example: + // + // +== ascent <--------->== +===================== + // | | | | + // | | ggggg | | ascent <---------> + // | | g gg | | | | + // | baseline < glyph | | | ggggg | + // | | g | -- middle of button -- | | g gg | + // | descent < ggggg | | baseline < glyph | + // | height |_________| | | g | + // | | descent < ggggg | + // | | height |_________| + // | | + // +======================= +===================== + // + // ascent = 4 + // button_height = 11 + // baseline = (11 + 4) / 2 - 1 = 6 + // + + int baseline_x = align_x + x_offset + m_left_padding; + int baseline_y = ((button_height + font().ascent()) / 2) - 1 + y_offset; + + // TODO: remove debug output fprintf(stderr, "%d | %d %d %d\n", height(), font().height(), font().ascent(), font().descent()); // give it ROT0 style coords - translateCoords(m_orientation, textx, texty, textw, texth); + translateCoords(m_orientation, baseline_x, baseline_y, button_width, button_height); - font().drawText(*drawable, - screenNumber(), - gc(), // graphic context - visual.c_str(), textlen, // string and string size - textx, texty, m_orientation); // position + font().drawText(*drawable, screenNumber(), gc(), + visual.c_str(), textlen, + baseline_x, baseline_y, m_orientation); } @@ -170,15 +196,15 @@ bool TextButton::textExceeds(int x_offset) { const FbString& visual = text().visual(); unsigned int textlen = visual.size(); - unsigned int textw = width(); - unsigned int texth = height(); - translateSize(m_orientation, textw, texth); + unsigned int button_width = width(); + unsigned int button_height = height(); + translateSize(m_orientation, button_width, button_height); - FbTk::doAlignment(textw - x_offset - m_left_padding - m_right_padding, + FbTk::doAlignment(button_width - x_offset - m_left_padding - m_right_padding, bevel(), justify(), font(), visual.data(), visual.size(), textlen); // return new text len - return visual.size()>textlen; + return visual.size() > textlen; } void TextButton::exposeEvent(XExposeEvent &event) { diff --git a/src/FbTk/TextButton.hh b/src/FbTk/TextButton.hh index a1c8b0e..eb0beca 100644 --- a/src/FbTk/TextButton.hh +++ b/src/FbTk/TextButton.hh @@ -56,7 +56,8 @@ public: void exposeEvent(XExposeEvent &event); - void renderForeground(FbDrawable &drawable); + //void renderForeground(FbDrawable &drawable); + void renderForeground(FbWindow &win, FbDrawable &drawable); FbTk::Justify justify() const { return m_justify; } const BiDiString &text() const { return m_text; } @@ -65,7 +66,6 @@ public: unsigned int textWidth() const; int bevel() const { return m_bevel; } - void renderForeground(FbWindow &win, FbDrawable &drawable); protected: virtual void drawText(int x_offset, int y_offset, FbDrawable *drawable_override); diff --git a/src/Toolbar.cc b/src/Toolbar.cc index 1c22440..f46b9e9 100644 --- a/src/Toolbar.cc +++ b/src/Toolbar.cc @@ -170,14 +170,12 @@ Toolbar::Frame::Frame(FbTk::EventHandler &evh, int screen_num): { FbTk::EventManager &evm = *FbTk::EventManager::instance(); - // add windows to eventmanager evm.add(evh, window); } Toolbar::Frame::~Frame() { FbTk::EventManager &evm = *FbTk::EventManager::instance(); - // remove windows from eventmanager evm.remove(window); } diff --git a/src/tests/testFont.cc b/src/tests/testFont.cc index 1ba63f7..3fdc0a6 100644 --- a/src/tests/testFont.cc +++ b/src/tests/testFont.cc @@ -19,13 +19,16 @@ // DEALINGS IN THE SOFTWARE. #include "FbTk/App.hh" +#include "FbTk/FbString.hh" #include "FbTk/FbWindow.hh" #include "FbTk/Font.hh" +#include "FbTk/TextButton.hh" #include "FbTk/EventHandler.hh" #include "FbTk/EventManager.hh" #include "FbTk/GContext.hh" #include "FbTk/Color.hh" #include "FbTk/FbString.hh" +#include "FbTk/StringUtil.hh" #include #include @@ -34,124 +37,103 @@ #include #include #include +#include using namespace std; -class App:public FbTk::App, public FbTk::EventHandler { + +class App:public FbTk::App, public FbTk::FbWindow, public FbTk::EventHandler { public: - App(const char *displayname, const string &foreground, const string background): + App(const char *displayname, const string& foreground, const string& background): FbTk::App(displayname), - m_win(DefaultScreen(display()), - 0, 0, 640, 480, KeyPressMask | ExposureMask) { - m_background = background; - m_foreground = foreground; - m_orient = FbTk::ROT0; - m_win.show(); - m_win.setBackgroundColor(FbTk::Color(background.c_str(), m_win.screenNumber())); - FbTk::EventManager::instance()->add(*this, m_win); - } - ~App() { + FbTk::FbWindow(DefaultScreen(this->FbTk::App::display()), 0, 0, 640, 480, KeyPressMask|ExposureMask|StructureNotifyMask), + m_gc(drawable()), + m_foreground(foreground.c_str(), screenNumber()), + m_background(background.c_str(), screenNumber()) { + + m_gc.setLineAttributes(1, FbTk::GContext::JOINMITER, FbTk::GContext::LINESOLID, FbTk::GContext::CAPNOTLAST); + m_gc.setForeground(m_foreground); + m_gc.setBackground(m_background); + setBackgroundColor(m_background); + + FbTk::EventManager::instance()->add(*this, *this); } + + ~App() { } + void keyPressEvent(XKeyEvent &ke) { KeySym ks; char keychar[1]; XLookupString(&ke, keychar, 1, &ks, 0); - if (ks == XK_Escape) + if (ks == XK_Escape) { end(); - /* - else { // toggle antialias - m_font.setAntialias(!m_font.isAntialias()); - cerr<clear(); + b->drawLine(m_gc.gc(), 0, b->height() / 2, b->width(), b->height() / 2); } + this->clear(); + } -/* - m_win.drawLine(wingc.gc(), - x, y + m_font.descent(), - x + text_w, y + m_font.descent()); - m_win.drawLine(wingc.gc(), - x, y - text_h, - x + text_w, y - text_h); -*/ - // draw the baseline in red - wingc.setForeground(FbTk::Color("red", m_win.screenNumber())); - m_win.drawLine(wingc.gc(), - x + bx1, y + by1, x + bx2, y+by2); - wingc.setForeground(FbTk::Color(m_foreground.c_str(), m_win.screenNumber())); - cerr<<"text size "<moveResize(i * w, 0, w, height); + } + redraw(); } - FbTk::Font &font() { return m_font; } - void setText(const std::string& text, const FbTk::Orientation orient) { m_text = text; m_orient = orient; } + void addText(const FbTk::BiDiString& text, FbTk::Font& font, const FbTk::Orientation orient) { + + FbTk::FbWindow* win = this; + FbTk::TextButton* button = new FbTk::TextButton(*win, font, text); + + button->setGC(m_gc.gc()); + button->setOrientation(orient); + button->setBackgroundColor(m_background); + button->show(); + + m_buttons.push_back(button); + } private: - string m_foreground, m_background; - FbTk::FbWindow m_win; - FbTk::Font m_font; - FbTk::Orientation m_orient; - string m_text; + vector m_buttons; + FbTk::GContext m_gc; + FbTk::Color m_foreground; + FbTk::Color m_background; }; + + int main(int argc, char **argv) { - //bool antialias = false; + + vector texts_and_fonts; FbTk::Orientation orient = FbTk::ROT0; - bool xft = false; - string fontname(""); string displayname(""); - string background("black"); - string foreground("white"); - string text("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_¯åäöÅÄÖ^~+=`\"!#¤%&/()=¡@£$½¥{[]}¶½§±"); - for (int a=1; a"<"<"<"<"< tf; + FbTk::StringUtil::stringtok(tf, texts_and_fonts[a], "|"); + if (tf.size() < 2) { + tf.push_back("default"); + } + + FbTk::Font* f = new FbTk::Font(0); + if (f->load(tf[1])) { + + if (orient && !f->validOrientation(orient)) { + cerr<<"Orientation not valid ("<