1 //
2 // "$Id: fl_set_fonts_xft.cxx 7913 2010-11-29 18:18:27Z greg.ercolano $"
3 //
4 // More font utilities for the Fast Light Tool Kit (FLTK).
5 //
6 // Copyright 1998-2010 by Bill Spitzak and others.
7 //
8 // This library is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU Library General Public
10 // License as published by the Free Software Foundation; either
11 // version 2 of the License, or (at your option) any later version.
12 //
13 // This library is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // Library General Public License for more details.
17 //
18 // You should have received a copy of the GNU Library General Public
19 // License along with this library; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 // USA.
22 //
23 // Please report all bugs and problems on the following page:
24 //
25 //     http://www.fltk.org/str.php
26 //
27 
28 #include <X11/Xft/Xft.h>
29 
30 // This function fills in the fltk font table with all the fonts that
31 // are found on the X server.  It tries to place the fonts into families
32 // and to sort them so the first 4 in a family are normal, bold, italic,
33 // and bold italic.
34 
35 // Bug: older versions calculated the value for *ap as a side effect of
36 // making the name, and then forgot about it. To avoid having to change
37 // the header files I decided to store this value in the last character
38 // of the font name array.
39 #define ENDOFBUFFER 127 // sizeof(Fl_Font.fontname)-1
40 
41 // turn a stored font name into a pretty name:
get_font_name(Fl_Font fnum,int * ap)42 const char* Fl::get_font_name(Fl_Font fnum, int* ap) {
43   Fl_Fontdesc *f = fl_fonts + fnum;
44   if (!f->fontname[0]) {
45     const char* p = f->name;
46     int type;
47     switch (p[0]) {
48     case 'B': type = FL_BOLD; break;
49     case 'I': type = FL_ITALIC; break;
50     case 'P': type = FL_BOLD | FL_ITALIC; break;
51     default:  type = 0; break;
52     }
53 
54   // NOTE: This can cause duplications in fonts that already have Bold or Italic in
55   // their "name". Maybe we need to find a cleverer way?
56     strlcpy(f->fontname, p+1, ENDOFBUFFER);
57     if (type & FL_BOLD) strlcat(f->fontname, " bold", ENDOFBUFFER);
58     if (type & FL_ITALIC) strlcat(f->fontname, " italic", ENDOFBUFFER);
59     f->fontname[ENDOFBUFFER] = (char)type;
60   }
61   if (ap) *ap = f->fontname[ENDOFBUFFER];
62   return f->fontname;
63 }
64 
65 ///////////////////////////////////////////////////////////
66 #define LOCAL_RAW_NAME_MAX 256
67 
68 extern "C" {
69 // sort returned fontconfig font names
name_sort(const void * aa,const void * bb)70 static int name_sort(const void *aa, const void *bb) {
71   // What should we do here? Just do a string compare for now...
72   // NOTE: This yeilds some oddities - in particular a Blah Bold font will be
73   // listed before Blah...
74   // Also - the fontconfig listing returns some faces that are effectively duplicates
75   // as far as fltk is concerned, e.g. where there are ko or ja variants that we
76   // can't distinguish (since we are not yet fully UTF-*) - should we strip them here?
77   return strcasecmp(*(char**)aa, *(char**)bb);
78 } // end of name_sort
79 } // end of extern C section
80 
81 
82 // Read the "pretty" name we have derived from fontconfig then convert
83 // it into the format fltk uses internally for Xft names...
84 // This is just a mess - I should have tokenised the strings and gone from there,
85 // but I really thought this would be easier!
make_raw_name(char * raw,char * pretty)86 static void make_raw_name(char *raw, char *pretty)
87 {
88   // Input name will be "Some Name:style = Bold Italic" or whatever
89   // The plan is this:
90   // - the first char in the "raw" name becomes either I, B, P or " " for
91   //   italic, bold, bold italic or normal - this seems to be the fltk way...
92 
93   char *style = strchr(pretty, ':');
94   char *last = pretty;
95 
96   if (style)
97   {
98     last = style + strnlen(style, ENDOFBUFFER) - 2; *style = 0; // Terminate "name" string
99     style ++;   // point to start of style section
100   }
101 
102   // It is still possible that the "pretty" name has multiple comma separated entries
103   // I've seen this often in CJK fonts, for example... Keep only the first one... This
104   // is not ideal, the CJK fonts often have the name in utf8 in several languages. What
105   // we ought to do is use fontconfig to query the available languages and pick one... But which?
106 #if 0 // loop to keep the LAST name entry...
107   char *nm1 = pretty;
108   char *nm2 = strchr(nm1, ',');
109   while(nm2) {
110     nm1 = nm2 + 1;
111     nm2 = strchr(nm1, ',');
112   }
113   raw[0] = ' '; raw[1] = 0; // Default start of "raw name" text
114   strncat(raw, nm1, LOCAL_RAW_NAME_MAX);
115 #else // keep the first remaining name entry
116   char *nm2 = strchr(pretty, ',');
117   if(nm2) *nm2 = 0; // terminate name after first entry
118   raw[0] = ' '; raw[1] = 0; // Default start of "raw name" text
119   strncat(raw, pretty, LOCAL_RAW_NAME_MAX-1);
120 #endif
121   // At this point, the name is "marked" as regular...
122   if (style)
123   {
124 #define PLAIN   0
125 #define BOLD    1
126 #define ITALIC  2
127 #define BITALIC (BOLD | ITALIC)
128     int mods = PLAIN;
129     // Now try and parse the style string - look for the "=" sign
130     style = strchr(style, '=');
131     while ((style) && (style < last))
132     {
133       int type;
134       while ((*style == '=') || (*style == ' ') || (*style == '\t'))
135       {
136         style++; // Start of Style string
137         if ((style >= last) || (*style == 0)) continue;
138       }
139       type = toupper(style[0]);
140       switch (type)
141       {
142       // Things we might see: Regular Normal Bold Italic Oblique (??what??) Medium
143       // Roman Light Demi Sans SemiCondensed SuperBold Book... etc...
144       // Things we actually care about: Bold Italic Oblique SuperBold - Others???
145       case 'I':
146         if (strncasecmp(style, "Italic", 6) == 0)
147         {
148           mods |= ITALIC;
149         }
150         goto NEXT_STYLE;
151 
152       case 'B':
153         if (strncasecmp(style, "Bold", 4) == 0)
154         {
155           mods |= BOLD;
156         }
157         goto NEXT_STYLE;
158 
159       case 'O':
160         if (strncasecmp(style, "Oblique", 7) == 0)
161         {
162           mods |= ITALIC;
163         }
164         goto NEXT_STYLE;
165 
166       case 's':
167         if (strncasecmp(style, "SuperBold", 9) == 0)
168         {
169           mods |= BOLD;
170         }
171         goto NEXT_STYLE;
172 
173       default: // find the next gap
174         goto NEXT_STYLE;
175       } // switch end
176 NEXT_STYLE:
177       while ((*style != ' ') && (*style != '\t'))
178       {
179         style++;
180         if ((style >= last) || (*style == 0)) goto STYLE_DONE;
181       }
182     }
183 STYLE_DONE:
184     // Set the "modifier" character in the raw string
185     switch(mods)
186     {
187     case BOLD: raw[0] = 'B';
188       break;
189     case ITALIC: raw[0] = 'I';
190       break;
191     case BITALIC: raw[0] = 'P';
192       break;
193     default: raw[0] = ' ';
194       break;
195     }
196   }
197 } // make_raw_name
198 
199 ///////////////////////////////////////////////////////////
200 
201 static int fl_free_font = FL_FREE_FONT;
202 
203 // Uses the fontconfig lib to construct a list of all installed fonts.
204 // I tried using XftListFonts for this, but the API is tricky - and when
205 // I looked at the XftList* code, it calls the Fc* functions anyway, so...
206 //
207 // Also, for now I'm ignoring the "pattern_name" and just getting everything...
208 // AND I don't try and skip the fonts we've already loaded in the defaults.
209 // Blimey! What a hack!
set_fonts(const char * pattern_name)210 Fl_Font Fl::set_fonts(const char* pattern_name)
211 {
212   FcFontSet  *fnt_set;     // Will hold the list of fonts we find
213   FcPattern   *fnt_pattern; // Holds the generic "match all names" pattern
214   FcObjectSet *fnt_obj_set = 0; // Holds the generic "match all objects"
215 
216   int j; // loop iterator variable
217   int font_count; // Total number of fonts found to process
218   char **full_list; // The list of font names we build
219 
220   if (fl_free_font > FL_FREE_FONT) // already been here
221     return (Fl_Font)fl_free_font;
222 
223   fl_open_display(); // Just in case...
224 
225   // Make sure fontconfig is ready... is this necessary? The docs say it is
226   // safe to call it multiple times, so just go for it anyway!
227   if (!FcInit())
228   {
229     // What to do? Just return defaults...
230     return FL_FREE_FONT;
231   }
232 
233   // Create a search pattern that will match every font name - I think this
234   // does the Right Thing, but am not certain...
235   //
236   // This could possibly be "enhanced" to pay attention to the requested
237   // "pattern_name"?
238   fnt_pattern = FcPatternCreate();
239   fnt_obj_set = FcObjectSetBuild(FC_FAMILY, FC_STYLE, (void *)0);
240 
241   // Hopefully, this is a set of all the fonts...
242   fnt_set = FcFontList(0, fnt_pattern, fnt_obj_set);
243 
244   // We don't need the fnt_pattern any more, release it
245   FcPatternDestroy(fnt_pattern);
246 
247   // Now, if we got any fonts, iterate through them...
248   if (fnt_set)
249   {
250     char *stop;
251     char *start;
252     char *first;
253 
254     font_count = fnt_set->nfont; // How many fonts?
255 
256     // Allocate array of char*'s to hold the name strings
257     full_list = (char **)malloc(sizeof(char *) * font_count);
258 
259     // iterate through all the font patterns and get the names out...
260       for (j = 0; j < font_count; j++)
261       {
262       // NOTE: FcChar8 is a typedef of "unsigned char"...
263       FcChar8 *font; // String to hold the font's name
264 
265       // Convert from fontconfig internal pattern to human readable name
266       // NOTE: This WILL malloc storage, so we need to free it later...
267       font = FcNameUnparse(fnt_set->fonts[j]);
268 
269       // The returned strings look like this...
270       // Century Schoolbook:style=Bold Italic,fed kursiv,Fett Kursiv,...
271       // So the bit we want is up to the first comma - BUT some strings have
272       // more than one name, separated by, guess what?, a comma...
273       stop = start = first = 0;
274       stop = strchr((char *)font, ',');
275       start = strchr((char *)font, ':');
276       if ((stop) && (start) && (stop < start))
277       {
278         first = stop + 1; // discard first version of name
279         // find first comma *after* the end of the name
280         stop = strchr((char *)start, ',');
281       }
282       else
283       {
284         first = (char *)font; // name is just what was returned
285       }
286       // Truncate the name after the (english) modifiers description
287       if (stop)
288       {
289         *stop = 0; // Terminate the string at the first comma, if there is one
290       }
291 
292       // Copy the font description into our list
293       if (first == (char *)font)
294       { // The listed name is still OK
295         full_list[j] = (char *)font;
296       }
297       else
298       { // The listed name has been modified
299         full_list[j] = strdup(first);
300         // Free the font name storage
301         free (font);
302       }
303       // replace "style=Regular" so strcmp sorts it first
304       if (start) {
305         char *reg = strstr(full_list[j], "=Regular");
306         if (reg) reg[1]='.';
307       }
308     }
309 
310     // Release the fnt_set - we don't need it any more
311     FcFontSetDestroy(fnt_set);
312 
313     // Sort the list into alphabetic order
314     qsort(full_list, font_count, sizeof(*full_list), name_sort);
315 
316     // Now let us add the names we got to fltk's font list...
317     for (j = 0; j < font_count; j++)
318     {
319       if (full_list[j])
320       {
321         char xft_name[LOCAL_RAW_NAME_MAX];
322         char *stored_name;
323         // Parse the strings into FLTK-XFT style..
324         make_raw_name(xft_name, full_list[j]);
325         // NOTE: This just adds on AFTER the default fonts - no attempt is made
326         // to identify already loaded fonts. Is this bad?
327         stored_name = strdup(xft_name);
328         Fl::set_font((Fl_Font)(j + FL_FREE_FONT), stored_name);
329         fl_free_font ++;
330 
331         free(full_list[j]); // release that name from our internal array
332       }
333     }
334     // Now we are done with the list, release it fully
335     free(full_list);
336   }
337   return (Fl_Font)fl_free_font;
338 } // ::set_fonts
339 ////////////////////////////////////////////////////////////////
340 
341 
342 extern "C" {
int_sort(const void * aa,const void * bb)343 static int int_sort(const void *aa, const void *bb) {
344   return (*(int*)aa)-(*(int*)bb);
345 }
346 }
347 
348 ////////////////////////////////////////////////////////////////
349 
350 // Return all the point sizes supported by this font:
351 // Suprisingly enough Xft works exactly like fltk does and returns
352 // the same list. Except there is no way to tell if the font is scalable.
get_font_sizes(Fl_Font fnum,int * & sizep)353 int Fl::get_font_sizes(Fl_Font fnum, int*& sizep) {
354   Fl_Fontdesc *s = fl_fonts+fnum;
355   if (!s->name) s = fl_fonts; // empty slot in table, use entry 0
356 
357   fl_open_display();
358   XftFontSet* fs = XftListFonts(fl_display, fl_screen,
359                                 XFT_FAMILY, XftTypeString, s->name+1,
360 				(void *)0,
361                                 XFT_PIXEL_SIZE,
362 				(void *)0);
363   static int* array = 0;
364   static int array_size = 0;
365   if (fs->nfont >= array_size) {
366     delete[] array;
367     array = new int[array_size = fs->nfont+1];
368   }
369   array[0] = 0; int j = 1; // claim all fonts are scalable
370   for (int i = 0; i < fs->nfont; i++) {
371     double v;
372     if (XftPatternGetDouble(fs->fonts[i], XFT_PIXEL_SIZE, 0, &v) == XftResultMatch) {
373       array[j++] = int(v);
374     }
375   }
376   qsort(array+1, j-1, sizeof(int), int_sort);
377   XftFontSetDestroy(fs);
378   sizep = array;
379   return j;
380 }
381 
382 //
383 // End of "$Id: fl_set_fonts_xft.cxx 7913 2010-11-29 18:18:27Z greg.ercolano $".
384 //
385