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>
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:
14 // The above copyright notice and this permission notice shall be included in
15 // all copies or substantial portions of the Software.
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.
29 #include "Resource.hh"
36 # include <X11/Xft/Xft.h>
44 // #define FONTCACHE_DEBUG
47 static const char * const defaultFont
= "fixed";
49 static const char * const defaultXftFont
= "sans-serif";
57 FontCache(const Display
&dpy
);
60 XFontSet
findFontSet(const std::string
&fontsetname
);
62 XftFont
*findXftFont(const std::string
&fontname
, unsigned int screen
);
65 void release(const std::string
&fontname
, unsigned int screen
);
67 void clear(bool force
);
69 const Display
&_display
;
75 const std::string name
;
77 inline FontName(const std::string
&n
, unsigned int s
)
80 inline bool operator<(const FontName
&other
) const {
81 if (screen
!= other
.screen
)
82 return screen
< other
.screen
;
83 return name
< other
.name
;
88 XFontSet
const fontset
;
90 XftFont
* const xftfont
;
92 void * const xftfont
; // avoid #ifdef spaghetti below...
96 : fontset(0), xftfont(0), count(0u)
98 inline FontRef(XFontSet
const fs
)
99 : fontset(fs
), xftfont(0), count(1u)
102 inline FontRef(XftFont
* const ft
)
103 : fontset(0), xftfont(ft
), count(1u)
108 typedef std::map
<FontName
,FontRef
> Cache
;
109 typedef Cache::value_type CacheItem
;
114 static FontCache
*fontcache
= 0;
117 void createFontCache(const Display
&display
) {
118 assert(fontcache
== 0);
119 fontcache
= new FontCache(display
);
123 void destroyFontCache(void) {
149 typedef std::vector
<std::string
> xlfd_vector
;
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
);
157 for (x
= 0; x
< xp_count
&& it
!= end
&& *it
; ++x
) {
159 ++save
; // skip the '-'
160 while (++it
!= end
&& *it
!= '-')
162 vec
[x
].assign(save
, it
);
166 return xlfd_vector();
174 bt::FontCache::FontCache(const Display
&dpy
)
178 xft_initialized
= XftInit(NULL
) && XftInitFtLibrary();
183 bt::FontCache::~FontCache(void)
187 XFontSet
bt::FontCache::findFontSet(const std::string
&fontsetname
) {
188 if (fontsetname
.empty())
189 return findFontSet(defaultFont
);
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()) {
198 #ifdef FONTCACHE_DEBUG
199 fprintf(stderr
, "bt::FontCache: ref set '%s'\n", fontsetname
.c_str());
200 #endif // FONTCACHE_DEBUG
203 return it
->second
.fontset
;
207 char **missing
, *def
= "-";
211 fs
= XCreateFontSet(_display
.XDisplay(), fontsetname
.c_str(),
212 &missing
, &nmissing
, &def
);
215 // missing characters, unload and try again below
216 XFreeFontSet(_display
.XDisplay(), fs
);
221 XFreeStringList(missing
);
224 #ifdef FONTCACHE_DEBUG
225 fprintf(stderr
, "bt::FontCache: add set '%s'\n", fontsetname
.c_str());
226 #endif // FONTCACHE_DEBUG
228 cache
.insert(CacheItem(fn
, FontRef(fs
)));
229 return fs
; // created fontset
234 fontset is missing some charsets, adjust the fontlist to allow
235 Xlib to automatically find the needed fonts.
237 xlfd_vector vec
= parse_xlfd(fontsetname
);
238 std::string newname
= fontsetname
;
241 ",-*-*-" + vec
[xp_weight
] + "-" + vec
[xp_slant
] + "-*-*-" +
242 vec
[xp_pixels
] + "-*-*-*-*-*-*-*,-*-*-*-*-*-*-" + vec
[xp_pixels
] +
243 "-" + vec
[xp_points
] + "-*-*-*-*-*-*,*";
245 newname
+= "-*-*-*-*-*-*-*-*-*-*-*-*-*-*,*";
248 fs
= XCreateFontSet(_display
.XDisplay(), newname
.c_str(),
249 &missing
, &nmissing
, &def
);
251 for (int x
= 0; x
< nmissing
; ++x
)
252 fprintf(stderr
, "Warning: missing charset '%s' in fontset\n",
256 XFreeStringList(missing
);
258 #ifdef FONTCACHE_DEBUG
259 fprintf(stderr
, "bt::FontCache: add set '%s'\n", fontsetname
.c_str());
260 #endif // FONTCACHE_DEBUG
262 cache
.insert(CacheItem(fn
, FontRef(fs
)));
268 XftFont
*bt::FontCache::findXftFont(const std::string
&fontname
,
269 unsigned int screen
) {
270 if (!xft_initialized
)
273 if (fontname
.empty())
274 return findXftFont(defaultXftFont
, screen
);
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()) {
282 assert(it
->first
.screen
== screen
);
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
290 return it
->second
.xftfont
;
297 XListFonts(_display
.XDisplay(), fontname
.c_str(), 1, &unused
);
299 // if fontname is a valid XLFD or alias, use a fontset instead of Xft
301 XFreeFontNames(list
);
303 #ifdef FONTCACHE_DEBUG
304 fprintf(stderr
, "bt::FontCache: skp Xft%u '%s'\n",
305 screen
, fontname
.c_str());
306 #endif // FONTCACHE_DEBUG
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";
315 ret
= XftFontOpenName(_display
.XDisplay(), screen
, n
.c_str());
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
);
324 #ifdef FONTCACHE_DEBUG
325 fprintf(stderr
, "bt::FontCache: add Xft%u '%s'\n",
326 screen
, fontname
.c_str());
327 #endif // FONTCACHE_DEBUG
330 cache
.insert(CacheItem(fn
, FontRef(ret
)));
336 void bt::FontCache::release(const std::string
&fontname
, unsigned int screen
) {
337 if (fontname
.empty()) {
340 release(defaultXftFont
, screen
);
343 release(defaultFont
, screen
);
347 #ifdef FONTCACHE_DEBUG
348 fprintf(stderr
, "bt::FontCache: rel '%s'\n", fontname
.c_str());
349 #endif // FONTCACHE_DEBUG
351 assert(!fontname
.empty());
352 FontName
fn(fontname
, screen
);
353 Cache::iterator it
= cache
.find(fn
);
355 assert(it
!= cache
.end() && it
->second
.count
> 0);
360 void bt::FontCache::clear(bool force
) {
361 Cache::iterator it
= cache
.begin();
362 if (it
== cache
.end())
363 return; // nothing to do
365 #ifdef FONTCACHE_DEBUG
366 fprintf(stderr
, "bt::FontCache: clearing cache, %u entries\n", cache
.size());
367 #endif // FONTCACHE_DEBUG
369 while (it
!= cache
.end()) {
370 if (it
->second
.count
!= 0 && !force
) {
375 #ifdef FONTCACHE_DEBUG
376 fprintf(stderr
, "bt::FontCache: fre '%s'\n", it
->first
.name
.c_str());
377 #endif // FONTCACHE_DEBUG
379 if (it
->second
.fontset
)
380 XFreeFontSet(_display
.XDisplay(), it
->second
.fontset
);
382 if (it
->second
.xftfont
)
383 XftFontClose(_display
.XDisplay(), it
->second
.xftfont
);
386 Cache::iterator r
= it
++;
390 #ifdef FONTCACHE_DEBUG
391 fprintf(stderr
, "bt::FontCache: cleared, %u entries remain\n", cache
.size());
392 #endif // FONTCACHE_DEBUG
396 XFontSet
bt::Font::fontSet(void) const {
400 _fontset
= fontcache
->findFontSet(_fontname
);
406 XftFont
*bt::Font::xftFont(unsigned int screen
) const {
407 if (_screen
!= ~0u && _screen
== screen
)
411 _xftfont
= fontcache
->findXftFont(_fontname
, _screen
);
417 void bt::Font::unload(void) {
419 yes, we really want to check _fontset and _xftfont separately.
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
426 fontcache
->release(_fontname
, ~0u); // fontsets have no screen
431 fontcache
->release(_fontname
, _screen
);
438 void bt::Font::clearCache(void)
439 { fontcache
->clear(false); }
442 unsigned int bt::textHeight(unsigned int screen
, const Font
&font
) {
444 const XftFont
* const f
= font
.xftFont(screen
);
446 return f
->ascent
+ f
->descent
;
449 return XExtentsOfFontSet(font
.fontSet())->max_ink_extent
.height
;
453 unsigned int bt::textIndent(unsigned int screen
, const Font
&font
) {
455 const XftFont
* const f
= font
.xftFont(screen
);
460 XFontSetExtents
*e
= XExtentsOfFontSet(font
.fontSet());
461 return e
->max_ink_extent
.height
+ e
->max_ink_extent
.y
;
465 bt::Rect
bt::textRect(unsigned int screen
, const Font
&font
,
466 const bt::ustring
&text
) {
467 const unsigned int indent
= textIndent(screen
, font
);
470 XftFont
* const f
= font
.xftFont(screen
);
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
);
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
);
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
);
495 // align vertically (center for now)
496 tr
.setY(rect
.y() + ((rect
.height() - tr
.height()) / 2));
498 // align horizontally
501 tr
.setX(rect
.x() + rect
.width() - tr
.width() - 1);
505 tr
.setX(rect
.x() + (rect
.width() - tr
.width()) / 2);
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());
521 XftFont
* const f
= font
.xftFont(pen
.screen());
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());
530 XftDrawString32(pen
.xftDraw(drawable
), &col
, f
,
531 tr
.x() + indent
, tr
.y() + f
->ascent
,
532 reinterpret_cast<const FcChar32
*>(text
.data()),
538 const std::string str
= toLocale(text
);
539 XmbDrawString(pen
.XDisplay(), drawable
, font
.fontSet(), pen
.gc(),
541 tr
.y() - XExtentsOfFontSet(font
.fontSet())->max_ink_extent
.y
,
542 str
.c_str(), str
.length());
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();
552 assert(ellide
.length() < (count
/ 2));
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),
561 bt::ustring
bt::ellideText(const bt::ustring
&text
,
562 unsigned int max_width
,
563 const bt::ustring
&ellide
,
565 const bt::Font
&font
) {
566 bt::ustring visible
= text
;
567 bt::Rect r
= bt::textRect(screen
, font
, visible
);
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
);
577 visible
= ellide
; // couldn't ellide enough
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
)
591 if (res
.find("center") != std::string::npos
)
593 if (res
.find("right") != std::string::npos
)
595 return default_align
;