1 // -*- mode: C++; indent-tabs-mode: nil; c-basic-offset: 2; -*-
2 // Font.cc for Blackbox - an X11 Window manager
3 // Copyright (c) 2001 - 2005 Sean 'Shaleh' Perry <shaleh@debian.org>
4 // Copyright (c) 1997 - 2000, 2002 - 2005
5 // Bradley T Hughes <bhughes at trolltech.com>
6 //
7 // Permission is hereby granted, free of charge, to any person obtaining a
8 // copy of this software and associated documentation files (the "Software"),
9 // to deal in the Software without restriction, including without limitation
10 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 // and/or sell copies of the Software, and to permit persons to whom the
12 // Software is furnished to do so, subject to the following conditions:
13 //
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
16 //
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 // DEALINGS IN THE SOFTWARE.
24
25 #include "Font.hh"
26 #include "Color.hh"
27 #include "Display.hh"
28 #include "Pen.hh"
29 #include "Resource.hh"
30
31 #include <map>
32 #include <vector>
33
34 #include <X11/Xlib.h>
35 #ifdef XFT
36 # include <X11/Xft/Xft.h>
37 #endif
38
39 #include <assert.h>
40 #include <ctype.h>
41 #include <locale.h>
42 #include <stdio.h>
43
44 // #define FONTCACHE_DEBUG
45
46
47 static const char * const defaultFont = "fixed";
48 #ifdef XFT
49 static const char * const defaultXftFont = "sans-serif";
50 #endif
51
52
53 namespace bt {
54
55 class FontCache {
56 public:
57 FontCache(const Display &dpy);
58 ~FontCache(void);
59
60 XFontSet findFontSet(const std::string &fontsetname);
61 #ifdef XFT
62 XftFont *findXftFont(const std::string &fontname, unsigned int screen);
63 #endif
64
65 void release(const std::string &fontname, unsigned int screen);
66
67 void clear(bool force);
68
69 const Display &_display;
70 #ifdef XFT
71 bool xft_initialized;
72 #endif
73
74 struct FontName {
75 const std::string name;
76 unsigned int screen;
FontNamebt::FontCache::FontName77 inline FontName(const std::string &n, unsigned int s)
78 : name(n), screen(s)
79 { }
operator <bt::FontCache::FontName80 inline bool operator<(const FontName &other) const {
81 if (screen != other.screen)
82 return screen < other.screen;
83 return name < other.name;
84 }
85 };
86
87 struct FontRef {
88 XFontSet const fontset;
89 #ifdef XFT
90 XftFont * const xftfont;
91 #else
92 void * const xftfont; // avoid #ifdef spaghetti below...
93 #endif
94 unsigned int count;
FontRefbt::FontCache::FontRef95 inline FontRef(void)
96 : fontset(0), xftfont(0), count(0u)
97 { }
FontRefbt::FontCache::FontRef98 inline FontRef(XFontSet const fs)
99 : fontset(fs), xftfont(0), count(1u)
100 { }
101 #ifdef XFT
FontRefbt::FontCache::FontRef102 inline FontRef(XftFont * const ft)
103 : fontset(0), xftfont(ft), count(1u)
104 { }
105 #endif
106 };
107
108 typedef std::map<FontName,FontRef> Cache;
109 typedef Cache::value_type CacheItem;
110 Cache cache;
111 };
112
113
114 static FontCache *fontcache = 0;
115
116
createFontCache(const Display & display)117 void createFontCache(const Display &display) {
118 assert(fontcache == 0);
119 fontcache = new FontCache(display);
120 }
121
122
destroyFontCache(void)123 void destroyFontCache(void) {
124 delete fontcache;
125 fontcache = 0;
126 }
127
128
129 // xlfd parser
130 enum xlfd_parts {
131 xp_foundry,
132 xp_family,
133 xp_weight,
134 xp_slant,
135 xp_width,
136 xp_addstyle,
137 xp_pixels,
138 xp_points,
139 xp_resx,
140 xp_resy,
141 xp_space,
142 xp_avgwidth,
143 xp_regsitry,
144 xp_encoding,
145 xp_count
146 };
147
148
149 typedef std::vector<std::string> xlfd_vector;
parse_xlfd(const std::string & xlfd)150 xlfd_vector parse_xlfd(const std::string &xlfd) {
151 std::string::const_iterator it = xlfd.begin(), end = xlfd.end(), save;
152 if (it == end || !*it || *it != '-')
153 return xlfd_vector();
154 xlfd_vector vec(xp_count);
155
156 int x;
157 for (x = 0; x < xp_count && it != end && *it; ++x) {
158 save = it;
159 ++save; // skip the '-'
160 while (++it != end && *it != '-')
161 ;
162 vec[x].assign(save, it);
163 }
164
165 if (x != xp_count)
166 return xlfd_vector();
167 return vec;
168 }
169
170 } // namespace bt
171
172
173
FontCache(const Display & dpy)174 bt::FontCache::FontCache(const Display &dpy)
175 : _display(dpy)
176 {
177 #ifdef XFT
178 xft_initialized = XftInit(NULL) && XftInitFtLibrary();
179 #endif
180 }
181
182
~FontCache(void)183 bt::FontCache::~FontCache(void)
184 { clear(true); }
185
186
findFontSet(const std::string & fontsetname)187 XFontSet bt::FontCache::findFontSet(const std::string &fontsetname) {
188 if (fontsetname.empty())
189 return findFontSet(defaultFont);
190
191 // see if the font is in the cache
192 assert(!fontsetname.empty());
193 FontName fn(fontsetname, ~0u);
194 Cache::iterator it = cache.find(fn);
195 if (it != cache.end()) {
196 // found it
197
198 #ifdef FONTCACHE_DEBUG
199 fprintf(stderr, "bt::FontCache: ref set '%s'\n", fontsetname.c_str());
200 #endif // FONTCACHE_DEBUG
201
202 ++it->second.count;
203 return it->second.fontset;
204 }
205
206 XFontSet fs;
207 char **missing, *def = "-";
208 int nmissing;
209
210 // load the fontset
211 fs = XCreateFontSet(_display.XDisplay(), fontsetname.c_str(),
212 &missing, &nmissing, &def);
213 if (fs) {
214 if (nmissing) {
215 // missing characters, unload and try again below
216 XFreeFontSet(_display.XDisplay(), fs);
217 fs = 0;
218 }
219
220 if (missing)
221 XFreeStringList(missing);
222
223 if (fs) {
224 #ifdef FONTCACHE_DEBUG
225 fprintf(stderr, "bt::FontCache: add set '%s'\n", fontsetname.c_str());
226 #endif // FONTCACHE_DEBUG
227
228 cache.insert(CacheItem(fn, FontRef(fs)));
229 return fs; // created fontset
230 }
231 }
232
233 /*
234 fontset is missing some charsets, adjust the fontlist to allow
235 Xlib to automatically find the needed fonts.
236 */
237 xlfd_vector vec = parse_xlfd(fontsetname);
238 std::string newname = fontsetname;
239 if (!vec.empty()) {
240 newname +=
241 ",-*-*-" + vec[xp_weight] + "-" + vec[xp_slant] + "-*-*-" +
242 vec[xp_pixels] + "-*-*-*-*-*-*-*,-*-*-*-*-*-*-" + vec[xp_pixels] +
243 "-" + vec[xp_points] + "-*-*-*-*-*-*,*";
244 } else {
245 newname += "-*-*-*-*-*-*-*-*-*-*-*-*-*-*,*";
246 }
247
248 fs = XCreateFontSet(_display.XDisplay(), newname.c_str(),
249 &missing, &nmissing, &def);
250 if (nmissing) {
251 for (int x = 0; x < nmissing; ++x)
252 fprintf(stderr, "Warning: missing charset '%s' in fontset\n",
253 missing[x]);
254 }
255 if (missing)
256 XFreeStringList(missing);
257
258 #ifdef FONTCACHE_DEBUG
259 fprintf(stderr, "bt::FontCache: add set '%s'\n", fontsetname.c_str());
260 #endif // FONTCACHE_DEBUG
261
262 cache.insert(CacheItem(fn, FontRef(fs)));
263 return fs;
264 }
265
266
267 #ifdef XFT
findXftFont(const std::string & fontname,unsigned int screen)268 XftFont *bt::FontCache::findXftFont(const std::string &fontname,
269 unsigned int screen) {
270 if (!xft_initialized)
271 return 0;
272
273 if (fontname.empty())
274 return findXftFont(defaultXftFont, screen);
275
276 // see if the font is in the cache
277 assert(!fontname.empty());
278 FontName fn(fontname, screen);
279 Cache::iterator it = cache.find(fn);
280 if (it != cache.end()) {
281 // found it
282 assert(it->first.screen == screen);
283
284 #ifdef FONTCACHE_DEBUG
285 fprintf(stderr, "bt::FontCache: %s Xft%u '%s'\n",
286 it->second.xftfont ? "ref" : "skp",
287 screen, fontname.c_str());
288 #endif // FONTCACHE_DEBUG
289 ++it->second.count;
290 return it->second.xftfont;
291 }
292
293 XftFont *ret = 0;
294 bool use_xft = true;
295 int unused = 0;
296 char **list =
297 XListFonts(_display.XDisplay(), fontname.c_str(), 1, &unused);
298 if (list != NULL) {
299 // if fontname is a valid XLFD or alias, use a fontset instead of Xft
300 use_xft = false;
301 XFreeFontNames(list);
302
303 #ifdef FONTCACHE_DEBUG
304 fprintf(stderr, "bt::FontCache: skp Xft%u '%s'\n",
305 screen, fontname.c_str());
306 #endif // FONTCACHE_DEBUG
307 }
308
309 if (use_xft) {
310 // Xft can't do antialiasing on 8bpp very well
311 std::string n = fontname;
312 if (_display.screenInfo(screen).depth() <= 8)
313 n += ":antialias=false";
314
315 ret = XftFontOpenName(_display.XDisplay(), screen, n.c_str());
316 if (ret == NULL) {
317 // Xft will never return NULL, but it doesn't hurt to be cautious
318 fprintf(stderr, "bt::Font: couldn't load Xft%u '%s'\n",
319 screen, fontname.c_str());
320 ret = XftFontOpenName(_display.XDisplay(), screen, defaultXftFont);
321 }
322 assert(ret != NULL);
323
324 #ifdef FONTCACHE_DEBUG
325 fprintf(stderr, "bt::FontCache: add Xft%u '%s'\n",
326 screen, fontname.c_str());
327 #endif // FONTCACHE_DEBUG
328 }
329
330 cache.insert(CacheItem(fn, FontRef(ret)));
331 return ret;
332 }
333 #endif
334
335
release(const std::string & fontname,unsigned int screen)336 void bt::FontCache::release(const std::string &fontname, unsigned int screen) {
337 if (fontname.empty()) {
338 #ifdef XFT
339 if (screen != ~0u)
340 release(defaultXftFont, screen);
341 else
342 #endif // XFT
343 release(defaultFont, screen);
344 return;
345 }
346
347 #ifdef FONTCACHE_DEBUG
348 fprintf(stderr, "bt::FontCache: rel '%s'\n", fontname.c_str());
349 #endif // FONTCACHE_DEBUG
350
351 assert(!fontname.empty());
352 FontName fn(fontname, screen);
353 Cache::iterator it = cache.find(fn);
354
355 assert(it != cache.end() && it->second.count > 0);
356 --it->second.count;
357 }
358
359
clear(bool force)360 void bt::FontCache::clear(bool force) {
361 Cache::iterator it = cache.begin();
362 if (it == cache.end())
363 return; // nothing to do
364
365 #ifdef FONTCACHE_DEBUG
366 fprintf(stderr, "bt::FontCache: clearing cache, %u entries\n", cache.size());
367 #endif // FONTCACHE_DEBUG
368
369 while (it != cache.end()) {
370 if (it->second.count != 0 && !force) {
371 ++it;
372 continue;
373 }
374
375 #ifdef FONTCACHE_DEBUG
376 fprintf(stderr, "bt::FontCache: fre '%s'\n", it->first.name.c_str());
377 #endif // FONTCACHE_DEBUG
378
379 if (it->second.fontset)
380 XFreeFontSet(_display.XDisplay(), it->second.fontset);
381 #ifdef XFT
382 if (it->second.xftfont)
383 XftFontClose(_display.XDisplay(), it->second.xftfont);
384 #endif
385
386 Cache::iterator r = it++;
387 cache.erase(r);
388 }
389
390 #ifdef FONTCACHE_DEBUG
391 fprintf(stderr, "bt::FontCache: cleared, %u entries remain\n", cache.size());
392 #endif // FONTCACHE_DEBUG
393 }
394
395
fontSet(void) const396 XFontSet bt::Font::fontSet(void) const {
397 if (_fontset)
398 return _fontset;
399
400 _fontset = fontcache->findFontSet(_fontname);
401 return _fontset;
402 }
403
404
405 #ifdef XFT
xftFont(unsigned int screen) const406 XftFont *bt::Font::xftFont(unsigned int screen) const {
407 if (_screen != ~0u && _screen == screen)
408 return _xftfont;
409
410 _screen = screen;
411 _xftfont = fontcache->findXftFont(_fontname, _screen);
412 return _xftfont;
413 }
414 #endif
415
416
unload(void)417 void bt::Font::unload(void) {
418 /*
419 yes, we really want to check _fontset and _xftfont separately.
420
421 if the user has called fontSet() and xftFont(), then the _fontname
422 in the cache will be counted multiple times, so we will need to
423 release multiple times
424 */
425 if (_fontset)
426 fontcache->release(_fontname, ~0u); // fontsets have no screen
427 _fontset = 0;
428
429 #ifdef XFT
430 if (_xftfont)
431 fontcache->release(_fontname, _screen);
432 _xftfont = 0;
433 _screen = ~0u;
434 #endif
435 }
436
437
clearCache(void)438 void bt::Font::clearCache(void)
439 { fontcache->clear(false); }
440
441
textHeight(unsigned int screen,const Font & font)442 unsigned int bt::textHeight(unsigned int screen, const Font &font) {
443 #ifdef XFT
444 const XftFont * const f = font.xftFont(screen);
445 if (f)
446 return f->ascent + f->descent;
447 #endif
448
449 return XExtentsOfFontSet(font.fontSet())->max_ink_extent.height;
450 }
451
452
textIndent(unsigned int screen,const Font & font)453 unsigned int bt::textIndent(unsigned int screen, const Font &font) {
454 #ifdef XFT
455 const XftFont * const f = font.xftFont(screen);
456 if (f)
457 return f->descent;
458 #endif
459
460 XFontSetExtents *e = XExtentsOfFontSet(font.fontSet());
461 return e->max_ink_extent.height + e->max_ink_extent.y;
462 }
463
464
textRect(unsigned int screen,const Font & font,const bt::ustring & text)465 bt::Rect bt::textRect(unsigned int screen, const Font &font,
466 const bt::ustring &text) {
467 const unsigned int indent = textIndent(screen, font);
468
469 #ifdef XFT
470 XftFont * const f = font.xftFont(screen);
471 if (f) {
472 XGlyphInfo xgi;
473 XftTextExtents32(fontcache->_display.XDisplay(), f,
474 reinterpret_cast<const FcChar32 *>(text.data()),
475 text.length(), &xgi);
476 return Rect(xgi.x, 0, xgi.width - xgi.x + (indent * 2),
477 f->ascent + f->descent);
478 }
479 #endif
480
481 const std::string str = toLocale(text);
482 XRectangle ink, unused;
483 XmbTextExtents(font.fontSet(), str.c_str(), str.length(), &ink, &unused);
484 return Rect(ink.x, 0, ink.width - ink.x + (indent * 2),
485 XExtentsOfFontSet(font.fontSet())->max_ink_extent.height);
486 }
487
488
drawText(const Font & font,const Pen & pen,Drawable drawable,const Rect & rect,Alignment alignment,const bt::ustring & text)489 void bt::drawText(const Font &font, const Pen &pen,
490 Drawable drawable, const Rect &rect,
491 Alignment alignment, const bt::ustring &text) {
492 Rect tr = textRect(pen.screen(), font, text);
493 unsigned int indent = textIndent(pen.screen(), font);
494
495 // align vertically (center for now)
496 tr.setY(rect.y() + ((rect.height() - tr.height()) / 2));
497
498 // align horizontally
499 switch (alignment) {
500 case AlignRight:
501 tr.setX(rect.x() + rect.width() - tr.width() - 1);
502 break;
503
504 case AlignCenter:
505 tr.setX(rect.x() + (rect.width() - tr.width()) / 2);
506 break;
507
508 default:
509 case AlignLeft:
510 tr.setX(rect.x());
511 }
512
513 #if 0
514 // draws the rect 'tr' in red... useful for debugging text placement
515 Pen red(pen.screen(), Color(255, 0, 0));
516 XDrawRectangle(red.XDisplay(), drawable, red.gc(),
517 tr.x(), tr.y(), tr.width(), tr.height());
518 #endif
519
520 #ifdef XFT
521 XftFont * const f = font.xftFont(pen.screen());
522 if (f) {
523 XftColor col;
524 col.color.red = pen.color().red() | pen.color().red() << 8;
525 col.color.green = pen.color().green() | pen.color().green() << 8;
526 col.color.blue = pen.color().blue() | pen.color().blue() << 8;
527 col.color.alpha = 0xffff;
528 col.pixel = pen.color().pixel(pen.screen());
529
530 XftDrawString32(pen.xftDraw(drawable), &col, f,
531 tr.x() + indent, tr.y() + f->ascent,
532 reinterpret_cast<const FcChar32 *>(text.data()),
533 text.length());
534 return;
535 }
536 #endif
537
538 const std::string str = toLocale(text);
539 XmbDrawString(pen.XDisplay(), drawable, font.fontSet(), pen.gc(),
540 tr.x() + indent,
541 tr.y() - XExtentsOfFontSet(font.fontSet())->max_ink_extent.y,
542 str.c_str(), str.length());
543 }
544
545
ellideText(const bt::ustring & text,size_t count,const bt::ustring & ellide)546 bt::ustring bt::ellideText(const bt::ustring &text, size_t count,
547 const bt::ustring &ellide) {
548 const bt::ustring::size_type len = text.length();
549 if (len <= count)
550 return text;
551
552 assert(ellide.length() < (count / 2));
553
554 bt::ustring ret = text;
555 return ret.replace(ret.begin() + (count / 2) - (ellide.length() / 2),
556 ret.end() - (count / 2) + ((ellide.length() / 2) + 1),
557 ellide);
558 }
559
560
ellideText(const bt::ustring & text,unsigned int max_width,const bt::ustring & ellide,unsigned int screen,const bt::Font & font)561 bt::ustring bt::ellideText(const bt::ustring &text,
562 unsigned int max_width,
563 const bt::ustring &ellide,
564 unsigned int screen,
565 const bt::Font &font) {
566 bt::ustring visible = text;
567 bt::Rect r = bt::textRect(screen, font, visible);
568
569 if (r.width() > max_width) {
570 const int min_c = (ellide.length() * 3) - 1;
571 int c = visible.length();
572 while (--c > min_c && r.width() > max_width) {
573 visible = bt::ellideText(text, c, ellide);
574 r = bt::textRect(screen, font, visible);
575 }
576 if (c <= min_c)
577 visible = ellide; // couldn't ellide enough
578 }
579
580 return visible;
581 }
582
583
alignResource(const Resource & resource,const char * name,const char * classname,Alignment default_align)584 bt::Alignment bt::alignResource(const Resource &resource,
585 const char* name, const char* classname,
586 Alignment default_align) {
587 std::string res = tolower(resource.read(name, classname));
588 // we use find since res could have spaces and such things in the string
589 if (res.find("left") != std::string::npos)
590 return AlignLeft;
591 if (res.find("center") != std::string::npos)
592 return AlignCenter;
593 if (res.find("right") != std::string::npos)
594 return AlignRight;
595 return default_align;
596 }
597