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