all repos — fluxbox @ 7b6ab828c7e5453a2720462156d165707935c9ef

custom fork of the fluxbox windowmanager

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
Mathias Gumz akira at fluxbox dot org
commit

7b6ab828c7e5453a2720462156d165707935c9ef

parent

032a23d1e790c5224194562a837cc80fc157ce9b

M src/FbTk/Font.ccsrc/FbTk/Font.cc

@@ -66,6 +66,7 @@ #include <list>

#include <map> #include <typeinfo> #include <langinfo.h> +#include <cstdio> #include <errno.h>

@@ -83,12 +84,10 @@

// use to map <font1>|<font2>|<font3> => <fontthatworks> typedef map<string, string> StringMap; typedef StringMap::iterator StringMapIt; -StringMap lookup_map; // stores <fontthatworks and the fontimp typedef map<string, FbTk::FontImp* > FontCache; typedef FontCache::iterator FontCacheIt; -FontCache font_cache; void resetEffects(FbTk::Font& font) {

@@ -99,6 +98,13 @@ font.setShadowColor(FbTk::Color("black", DefaultScreen(FbTk::App::instance()->display())));

font.setShadowOffY(2); 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

@@ -106,18 +112,17 @@

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 @@ }

} } +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 @@ // 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 + + char *locale_codeset = 0; + + // openbsd doesnt have this (yet?) #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) + 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 @@ }

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 <font1>|<font2>|<font3> 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 @@ for (name_it = names.begin(); name_it != names.end(); ++name_it) {

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 @@

#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;

@@ -249,6 +266,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 {
M src/FbTk/Font.hhsrc/FbTk/Font.hh

@@ -43,17 +43,19 @@ */

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 @@ @param size length of text in bytes

@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;

@@ -118,9 +118,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
M src/FbTk/TextButton.ccsrc/FbTk/TextButton.cc

@@ -23,6 +23,7 @@ #include "TextButton.hh"

#include "TextUtils.hh" #include "Font.hh" #include "GContext.hh" +#include <cstdio> namespace FbTk {

@@ -62,16 +63,18 @@ m_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 @@ }

/// 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 @@ drawText(0, 0, &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; + // + // 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 textx = align_x + x_offset + m_left_padding; - int texty = center_pos + y_offset; + int baseline_x = align_x + x_offset + m_left_padding; + int baseline_y = ((button_height + font().ascent()) / 2) - 1 + y_offset; - if (drawable == 0) - drawable = this; + // 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) {
M src/FbTk/TextButton.hhsrc/FbTk/TextButton.hh

@@ -56,7 +56,8 @@ bool exposure = false);

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 @@ FbTk::Orientation orientation() const { return m_orientation; }

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);
M src/Toolbar.ccsrc/Toolbar.cc

@@ -170,14 +170,12 @@ true) // override redirect

{ 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); }
M src/tests/testFont.ccsrc/tests/testFont.cc

@@ -19,13 +19,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER

// 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 <X11/Xutil.h> #include <X11/keysym.h>

@@ -34,124 +37,103 @@ #include <string>

#include <cstring> #include <cstdlib> #include <iostream> +#include <vector> 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); + 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() { - } + + ~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<<boolalpha; - cerr<<"antialias: "<<m_font.isAntialias()<<endl; - redraw(); - } */ + } + } + + virtual void handleEvent(XEvent& event) { + if (event.type == ConfigureNotify) { + resize(event.xconfigure.width, event.xconfigure.height); + } } + void exposeEvent(XExposeEvent &event) { redraw(); } void redraw() { - size_t text_w = m_font.textWidth(m_text.c_str(), m_text.size()); - int mult = 1; - if (m_orient == FbTk::ROT180) - mult = -1; - size_t text_h = m_font.height(); - int x = 640/2 - mult* text_w/2; - int y = 480/2 - mult*text_h/2; - m_win.clear(); - FbTk::GContext wingc(m_win.drawable()); - - int bx1 = 0; - int by1 = 0; - int bx2 = text_w; - int by2 = 0; + size_t i; + for (i = 0; i < m_buttons.size(); ++i) { + FbTk::TextButton* b = m_buttons[i]; + b->clear(); + b->drawLine(m_gc.gc(), 0, b->height() / 2, b->width(), b->height() / 2); + } + this->clear(); + } - switch (m_orient) { - case FbTk::ROT90: - by2 = bx2; - bx2 = 0; - break; - case FbTk::ROT180: - bx2 = -bx2; - break; - case FbTk::ROT270: - by2 = -bx2; - bx2 = 0; - break; - default: - break; + void resize(unsigned int width, unsigned int height) { + FbTk::FbWindow::resize(width, height); + unsigned w = width / m_buttons.size(); + size_t i; + for (i = 0; i < m_buttons.size(); ++i) { + m_buttons[i]->moveResize(i * w, 0, w, height); } + redraw(); + } -/* - 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 "<<text_w<<"x"<<text_h<<endl; - m_font.drawText(m_win, - 0, wingc.gc(), - m_text.c_str(), m_text.size(), - x, y, m_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); } - FbTk::Font &font() { return m_font; } - void setText(const std::string& text, const FbTk::Orientation orient) { m_text = text; m_orient = orient; } - private: - string m_foreground, m_background; - FbTk::FbWindow m_win; - FbTk::Font m_font; - FbTk::Orientation m_orient; - string m_text; + vector<FbTk::TextButton*> m_buttons; + FbTk::GContext m_gc; + FbTk::Color m_foreground; + FbTk::Color m_background; }; + + int main(int argc, char **argv) { - //bool antialias = false; + + vector<string> 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<argc; ++a) { - if (strcmp("-font", argv[a])==0 && a + 1 < argc) { - fontname = argv[++a]; - } else if (strcmp("-xft", argv[a])==0) { - xft = true; - } else if (strcmp("-display", argv[a]) == 0 && a + 1 < argc) { + string background("white"); + string foreground("black"); + + int a; + for (a = 1; a < argc; ++a) { + if (strcmp("-display", argv[a]) == 0 && a + 1 < argc) { displayname = argv[++a]; - } else if (strcmp("-text", argv[a]) == 0 && a + 1 < argc) { - text = argv[++a]; } else if (strcmp("-orient", argv[a]) == 0) { orient = (FbTk::Orientation) (atoi(argv[++a]) % 4); } else if (strcmp("-bg", argv[a]) == 0 && a + 1 < argc) {

@@ -159,35 +141,53 @@ background = argv[++a];

} else if (strcmp("-fg", argv[a]) == 0 && a + 1 < argc) { foreground = argv[++a]; } else if (strcmp("-h", argv[a]) == 0) { - cerr<<"Arguments: "<<endl; - cerr<<"-font <fontname>"<<endl; - // cerr<<"-antialias"<<endl; + cerr<<"Arguments: \"text|fontname\" [\"text|fontname2\"]"<<endl; cerr<<"-display <display>"<<endl; - cerr<<"-text <text>"<<endl; cerr<<"-orient"<<endl; cerr<<"-fg <foreground color>"<<endl; cerr<<"-bg <background color>"<<endl; cerr<<"-h"<<endl; exit(0); + } else { + texts_and_fonts.push_back(argv[a]); } - } - - App app(displayname.c_str(), foreground, background); - //app.font().setAntialias(antialias); - if (!app.font().load(fontname.c_str())) - cerr<<"Failed to load: "<<fontname<<endl; - if (orient && !app.font().validOrientation(orient)) { - cerr<<"Orientation not valid ("<<orient<<")"<<endl; - orient = FbTk::ROT0; + if (texts_and_fonts.empty()) { + texts_and_fonts.push_back("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789,.-_¯åäöÅÄÖ^~+=`\"!#¤%&/()=¡@£$½¥{[]}¶½§±|default"); } - // utf-8 it + + App app(displayname.c_str(), foreground, background); + app.show(); - cerr<<"Setting text: "<<text<<endl; - app.setText(FbTk::FbStringUtil::XStrToFb(text), orient); + for (a = 0; a < texts_and_fonts.size(); ++a) { + vector<string> 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 ("<<orient<<")"<<endl; + orient = FbTk::ROT0; + } + + app.addText(FbTk::FbStringUtil::XStrToFb(tf[0]), *f, orient); + + } else { + cerr<<"Failed to load: "<<tf[1]<<endl; + delete f; + } + } + + app.resize(app.width(), app.height()); app.redraw(); app.eventLoop(); - + + return 0; } +