Skip to content

Commit 302f0fc

Browse files
committed
TerminalFont (Linux): supports urxvt
Fixes #2105
1 parent 241aa3a commit 302f0fc

File tree

3 files changed

+350
-18
lines changed

3 files changed

+350
-18
lines changed

src/common/font.c

Lines changed: 310 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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+
197489
void ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style)
198490
{
199491
ffFontInit(font);

src/common/font.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ void ffFontInit(FFfont* font);
1515
void ffFontInitQt(FFfont* font, const char* data);
1616
void ffFontInitPango(FFfont* font, const char* data);
1717
void ffFontInitValues(FFfont* font, const char* name, const char* size);
18+
void ffFontInitXlfd(FFfont* font, const char* xlfd);
19+
void ffFontInitXft(FFfont* font, const char* xft);
1820
void ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style);
1921
void ffFontInitWithSpace(FFfont* font, const char* rawName);
2022
void ffFontDestroy(FFfont* font);

0 commit comments

Comments
 (0)