11#include "fastfetch.h"
22#include "util/FFlist.h"
33#include "util/FFstrbuf.h"
4+ #include "util/stringUtils.h"
45#include "common/font.h"
56
67#include <string.h>
@@ -127,20 +128,20 @@ static void fontPangoParseWord(const char** data, FFfont* font, FFstrbuf* altern
127128 }
128129
129130 if (
130- strncasecmp (wordStart , "Ultra" , 5 ) == 0 ||
131- strncasecmp (wordStart , "Extra" , 5 ) == 0 ||
132- strncasecmp (wordStart , "Semi" , 4 ) == 0 ||
133- strncasecmp (wordStart , "Demi" , 4 ) == 0 ||
134- strncasecmp (wordStart , "Normal" , wordLength ) == 0 ||
135- strncasecmp (wordStart , "Roman" , wordLength ) == 0 ||
136- strncasecmp (wordStart , "Oblique" , wordLength ) == 0 ||
137- strncasecmp (wordStart , "Italic" , wordLength ) == 0 ||
138- strncasecmp (wordStart , "Thin" , wordLength ) == 0 ||
139- strncasecmp (wordStart , "Light" , wordLength ) == 0 ||
140- strncasecmp (wordStart , "Bold" , wordLength ) == 0 ||
141- strncasecmp (wordStart , "Black" , wordLength ) == 0 ||
142- strncasecmp (wordStart , "Condensed" , wordLength ) == 0 ||
143- strncasecmp (wordStart , "Expanded" , wordLength ) == 0
131+ ffStrStartsWithIgnCase (wordStart , "Ultra" ) ||
132+ ffStrStartsWithIgnCase (wordStart , "Extra" ) ||
133+ ffStrStartsWithIgnCase (wordStart , "Semi" ) ||
134+ ffStrStartsWithIgnCase (wordStart , "Demi" ) ||
135+ ffStrStartsWithIgnCase (wordStart , "Normal" ) ||
136+ ffStrStartsWithIgnCase (wordStart , "Roman" ) ||
137+ ffStrStartsWithIgnCase (wordStart , "Oblique" ) ||
138+ ffStrStartsWithIgnCase (wordStart , "Italic" ) ||
139+ ffStrStartsWithIgnCase (wordStart , "Thin" ) ||
140+ ffStrStartsWithIgnCase (wordStart , "Light" ) ||
141+ ffStrStartsWithIgnCase (wordStart , "Bold" ) ||
142+ ffStrStartsWithIgnCase (wordStart , "Black" ) ||
143+ ffStrStartsWithIgnCase (wordStart , "Condensed" ) ||
144+ ffStrStartsWithIgnCase (wordStart , "Expanded" )
144145 ) {
145146 if (alternativeBuffer == NULL )
146147 {
@@ -151,10 +152,10 @@ static void fontPangoParseWord(const char** data, FFfont* font, FFstrbuf* altern
151152 strbufAppendNSExcludingC (alternativeBuffer , wordLength , wordStart , '-' );
152153
153154 if (
154- strncasecmp (wordStart , "Ultra " , 6 ) == 0 ||
155- strncasecmp (wordStart , "Extra " , 6 ) == 0 ||
156- strncasecmp (wordStart , "Semi " , 5 ) == 0 ||
157- strncasecmp (wordStart , "Demi " , 5 ) == 0
155+ ffStrStartsWithIgnCase (wordStart , "Ultra " ) ||
156+ ffStrStartsWithIgnCase (wordStart , "Extra " ) ||
157+ ffStrStartsWithIgnCase (wordStart , "Semi " ) ||
158+ ffStrStartsWithIgnCase (wordStart , "Demi " )
158159 ) {
159160 fontPangoParseWord (data , font , alternativeBuffer );
160161 }
@@ -194,6 +195,297 @@ void ffFontInitValues(FFfont* font, const char* name, const char* size)
194195 fontInitPretty (font );
195196}
196197
198+ void ffFontInitXlfd (FFfont * font , const char * xlfd )
199+ {
200+ assert (xlfd && * xlfd );
201+
202+ // https://en.wikipedia.org/wiki/X_logical_font_description
203+ ffFontInit (font );
204+
205+ // XLFD: -foundry-family-weight-slant-setwidth-addstyle-pixelsize-pointsize-xres-yres-spacing-averagewidth-charsetregistry-charsetencoding
206+ // It often starts with '-', which would create an empty first field. Skip it to align indexes.
207+ if (* xlfd == '-' )
208+ xlfd ++ ;
209+
210+ const char * pstart = xlfd ;
211+
212+ for (int field = 0 ; field < 14 ; field ++ )
213+ {
214+ const char * pend = strchr (pstart , '-' );
215+ uint32_t length = pend ? (uint32_t )(pend - pstart ) : (uint32_t ) strlen (pstart );
216+
217+ if (length > 0 )
218+ {
219+ if (field == 1 ) // family
220+ {
221+ ffStrbufAppendNS (& font -> name , length , pstart );
222+ }
223+ else if (field == 7 ) // pointsize (decipoints, preferred)
224+ {
225+ // parse positive integer from substring
226+ long deciPt = 0 ;
227+ bool ok = true;
228+ for (uint32_t i = 0 ; i < length ; i ++ )
229+ {
230+ char c = pstart [i ];
231+ if (c < '0' || c > '9' ) { ok = false; break ; }
232+ deciPt = deciPt * 10 + (c - '0' );
233+ }
234+
235+ if (ok && deciPt > 0 )
236+ {
237+ ffStrbufClear (& font -> size );
238+
239+ char tmp [32 ];
240+ if (deciPt % 10 == 0 )
241+ snprintf (tmp , sizeof (tmp ), "%ld" , deciPt / 10 );
242+ else
243+ snprintf (tmp , sizeof (tmp ), "%ld.%ld" , deciPt / 10 , deciPt % 10 );
244+
245+ ffStrbufAppendS (& font -> size , tmp );
246+ }
247+ }
248+ else if (field == 6 ) // pixelsize (fallback if pointsize missing/invalid)
249+ {
250+ if (font -> size .length == 0 )
251+ {
252+ long px = 0 ;
253+ bool ok = true;
254+ for (uint32_t i = 0 ; i < length ; i ++ )
255+ {
256+ char c = pstart [i ];
257+ if (c < '0' || c > '9' ) { ok = false; break ; }
258+ px = px * 10 + (c - '0' );
259+ }
260+
261+ if (ok && px > 0 )
262+ ffStrbufAppendNS (& font -> size , length , pstart );
263+ }
264+ }
265+ else if (field >= 2 && field <= 5 ) // weight/slant/setwidth/addstyle
266+ {
267+ // ignore "normal" (case-insensitive)
268+ if (!(length == 6 && ffStrStartsWithIgnCase (pstart , "normal" )))
269+ {
270+ FFstrbuf * style = (FFstrbuf * ) ffListAdd (& font -> styles );
271+ ffStrbufInitNS (style , length , pstart );
272+ }
273+ }
274+ }
275+
276+ if (!pend )
277+ break ;
278+
279+ pstart = pend + 1 ;
280+ }
281+
282+ fontInitPretty (font );
283+ }
284+
285+ void ffFontInitXft (FFfont * font , const char * xft )
286+ {
287+ assert (xft );
288+
289+ // https://en.wikipedia.org/wiki/Xft
290+ // Xft/Fontconfig pattern examples:
291+ // "DejaVu Sans Mono-10"
292+ // "monospace:size=10:weight=bold:slant=italic"
293+ // "Fira Code-12:style=Regular"
294+ // Goal: extract family(name), size, and some common styles.
295+
296+ ffFontInit (font );
297+
298+ // 1) Parse "head" part before first ':' => usually "family[-size]" (may include commas)
299+ const char * p = xft ;
300+
301+ while (* p == ' ' || * p == '\t' )
302+ ++ p ;
303+
304+ const char * headStart = p ;
305+ while (* p != '\0' && * p != ':' )
306+ ++ p ;
307+ const char * headEnd = p ;
308+
309+ // trim tail spaces
310+ while (headEnd > headStart && (headEnd [-1 ] == ' ' || headEnd [-1 ] == '\t' ))
311+ -- headEnd ;
312+
313+ // If multiple families are listed, take the first one (up to comma)
314+ for (const char * q = headStart ; q < headEnd ; ++ q )
315+ {
316+ if (* q == ',' )
317+ {
318+ headEnd = q ;
319+ while (headEnd > headStart && (headEnd [-1 ] == ' ' || headEnd [-1 ] == '\t' ))
320+ -- headEnd ;
321+ break ;
322+ }
323+ }
324+
325+ // Try parse trailing "-<number>" as size, otherwise entire head is name
326+ const char * dashPos = NULL ;
327+ const char * sizeStart = NULL ;
328+
329+ for (const char * q = headEnd ; q > headStart ; )
330+ {
331+ -- q ;
332+ if (* q == '-' && (q + 1 ) < headEnd && ffCharIsDigit (q [1 ]))
333+ {
334+ dashPos = q ;
335+ sizeStart = q + 1 ;
336+ break ;
337+ }
338+ }
339+
340+ if (dashPos )
341+ {
342+ bool ok = true;
343+ bool seenDigit = false;
344+ for (const char * q = sizeStart ; q < headEnd ; ++ q )
345+ {
346+ if (ffCharIsDigit (* q ))
347+ seenDigit = true;
348+ else if (* q == '.' )
349+ continue ;
350+ else
351+ {
352+ ok = false;
353+ break ;
354+ }
355+ }
356+
357+ if (ok && seenDigit )
358+ {
359+ const char * nameEnd = dashPos ;
360+ while (nameEnd > headStart && (nameEnd [-1 ] == ' ' || nameEnd [-1 ] == '\t' ))
361+ -- nameEnd ;
362+
363+ if (nameEnd > headStart )
364+ ffStrbufAppendNS (& font -> name , (uint32_t ) (nameEnd - headStart ), headStart );
365+
366+ if (headEnd > sizeStart )
367+ ffStrbufAppendNS (& font -> size , (uint32_t ) (headEnd - sizeStart ), sizeStart );
368+ }
369+ else
370+ {
371+ if (headEnd > headStart )
372+ ffStrbufAppendNS (& font -> name , (uint32_t ) (headEnd - headStart ), headStart );
373+ }
374+ }
375+ else
376+ {
377+ if (headEnd > headStart )
378+ ffStrbufAppendNS (& font -> name , (uint32_t ) (headEnd - headStart ), headStart );
379+ }
380+
381+ ffStrbufTrim (& font -> name , ' ' );
382+ ffStrbufTrim (& font -> name , '"' );
383+
384+ // 2) Parse key=value fields after ':' (Fontconfig-like). Fields separated by ':'.
385+ // Common keys: size, pixelsize, pointsize, style, weight, slant, width
386+ while (* p == ':' )
387+ {
388+ ++ p ;
389+
390+ // key
391+ const char * keyStart = p ;
392+ while (* p != '\0' && * p != '=' && * p != ':' )
393+ ++ p ;
394+ const char * keyEnd = p ;
395+
396+ if (* p != '=' )
397+ continue ; // skip tokens without '='
398+
399+ ++ p ; // skip '='
400+
401+ // value (until next ':', allow backslash-escaping)
402+ FF_STRBUF_AUTO_DESTROY value = ffStrbufCreate ();
403+
404+ while (* p != '\0' && * p != ':' )
405+ {
406+ if (* p == '\\' && p [1 ] != '\0' )
407+ {
408+ ++ p ;
409+ ffStrbufAppendC (& value , * p );
410+ ++ p ;
411+ continue ;
412+ }
413+
414+ ffStrbufAppendC (& value , * p );
415+ ++ p ;
416+ }
417+
418+ ffStrbufTrim (& value , ' ' );
419+ ffStrbufTrim (& value , '"' );
420+
421+ uint32_t keyLen = (uint32_t ) (keyEnd - keyStart );
422+
423+ // helper: set numeric size if not set yet
424+ const bool sizeEmpty = (font -> size .length == 0 );
425+ if (value .length > 0 )
426+ {
427+ if (keyLen == 4 && ffStrStartsWithIgnCase (keyStart , "size" ))
428+ {
429+ if (sizeEmpty )
430+ {
431+ if (ffStrbufEndsWithS (& value , "px" ) || ffStrbufEndsWithS (& value , "pt" ))
432+ ffStrbufSubstrBefore (& value , value .length - 2 );
433+
434+ if (ffCharIsDigit (value .chars [0 ]))
435+ ffStrbufAppend (& font -> size , & value );
436+ }
437+ }
438+ else if (
439+ (keyLen == 9 && ffStrStartsWithIgnCase (keyStart , "pixelsize" )) ||
440+ (keyLen == 9 && ffStrStartsWithIgnCase (keyStart , "pointsize" )) ||
441+ (keyLen == 8 && ffStrStartsWithIgnCase (keyStart , "fontsize" ))
442+ ) {
443+ if (sizeEmpty )
444+ {
445+ if (ffStrbufEndsWithS (& value , "px" ) || ffStrbufEndsWithS (& value , "pt" ))
446+ ffStrbufSubstrBefore (& value , value .length - 2 );
447+
448+ if (ffCharIsDigit (value .chars [0 ]))
449+ ffStrbufAppend (& font -> size , & value );
450+ }
451+ }
452+ else if (keyLen == 5 && ffStrStartsWithIgnCase (keyStart , "style" ))
453+ {
454+ // style may contain multiple words: "Bold Italic"
455+ const char * s = value .chars ;
456+ while (* s != '\0' )
457+ {
458+ while (* s == ' ' || * s == '\t' || * s == ',' )
459+ ++ s ;
460+
461+ const char * w = s ;
462+ while (* s != '\0' && * s != ' ' && * s != '\t' && * s != ',' )
463+ ++ s ;
464+
465+ if (s > w )
466+ {
467+ FFstrbuf * style = FF_LIST_ADD (FFstrbuf , font -> styles );
468+ ffStrbufInitNS (style , (uint32_t ) (s - w ), w );
469+ }
470+ }
471+ }
472+ else if (
473+ (keyLen == 6 && ffStrStartsWithIgnCase (keyStart , "weight" )) ||
474+ (keyLen == 5 && ffStrStartsWithIgnCase (keyStart , "slant" )) ||
475+ (keyLen == 5 && ffStrStartsWithIgnCase (keyStart , "width" ))
476+ ) {
477+ // normalize: remove '-' to align with other parsers ("Semi-Bold" -> "SemiBold")
478+ FFstrbuf * style = FF_LIST_ADD (FFstrbuf , font -> styles );
479+ ffStrbufInit (style );
480+ strbufAppendNSExcludingC (style , value .length , value .chars , '-' );
481+ ffStrbufTrim (style , ' ' );
482+ }
483+ }
484+ }
485+
486+ fontInitPretty (font );
487+ }
488+
197489void ffFontInitMoveValues (FFfont * font , FFstrbuf * name , FFstrbuf * size , FFstrbuf * style )
198490{
199491 ffFontInit (font );
0 commit comments