ohmu
·
2026-10-15
1#include "logo/logo.h"
2
3#include "common/jsonconfig.h"
4#include "common/strutil.h"
5
6#if !FF_HAVE_IMAGE_LOGO
7// The image logo types are still accepted by the parsers below in a build that can not render them,
8// so that the user is told which type is unavailable instead of being told the type does not exist.
9// The one that is deliberately left out is `raw`: it writes a pre-rendered byte stream through
10// untouched and needs no decoder, so a build without image logos can still display one.
11static bool logoTypeIsImage(FFLogoType type) {
12 switch (type) {
13 case FF_LOGO_TYPE_IMAGE_SIXEL:
14 case FF_LOGO_TYPE_IMAGE_KITTY:
15 case FF_LOGO_TYPE_IMAGE_KITTY_DIRECT:
16 case FF_LOGO_TYPE_IMAGE_KITTY_ICAT:
17 case FF_LOGO_TYPE_IMAGE_ITERM:
18 case FF_LOGO_TYPE_IMAGE_CHAFA:
19 return true;
20 default:
21 return false;
22 }
23}
24#endif
25
26// --sixel, --kitty, --kitty-direct, --kitty-icat and --iterm. They keep a branch of their own in
27// every build so that the error a build without image logos gives names the flag that was used,
28// rather than it being rejected as an unknown option. --chafa is not among them: it is gated on
29// FF_HAVE_CHAFA, which a build without image logos turns off as well, and it keeps the message it
30// has always given in that case.
31static void logoParseImageFlag(FFOptionsLogo* options, const char* key, const char* value, FFLogoType type) {
32#if FF_HAVE_IMAGE_LOGO
33 ffOptionParseString(key, value, &options->source);
34 options->type = type;
35#else
36 FF_UNUSED(options, key, value, type);
37 fputs("Error: Fastfetch was built without image logo support\n", stderr);
38 exit(477);
39#endif
40}
41
42const char* ffLogoPositionToString(FFLogoPosition position) {
43 switch (position) {
44 case FF_LOGO_POSITION_TOP:
45 return "top";
46 case FF_LOGO_POSITION_RIGHT:
47 return "right";
48 case FF_LOGO_POSITION_AUTO:
49 return "auto";
50 case FF_LOGO_POSITION_LEFT:
51 default:
52 return "left";
53 }
54}
55
56void ffOptionsInitLogo(FFOptionsLogo* options) {
57 ffStrbufInit(&options->source);
58 options->type = FF_LOGO_TYPE_AUTO;
59 for (uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i) {
60 ffStrbufInit(&options->colors[i]);
61 }
62 options->width = 0;
63 options->height = 0; // preserve aspect ratio
64 options->paddingTop = 0;
65 options->paddingBottom = 0;
66 options->paddingLeft = 0;
67 options->paddingRight = 4;
68 options->printRemaining = true;
69 options->preserveAspectRatio = false;
70 options->cache = FF_LOGO_CACHE_ON;
71 options->position = FF_LOGO_POSITION_LEFT;
72 options->animationFrame = FF_LOGO_ANIMATION_FRAME_FIRST;
73
74#if FF_HAVE_CHAFA
75 options->chafaFgOnly = false;
76 ffStrbufInitStatic(&options->chafaSymbols, "block+border+space-wide-inverted"); // Chafa default
77 options->chafaCanvasMode = UINT32_MAX;
78 options->chafaColorSpace = UINT32_MAX;
79 options->chafaDitherMode = UINT32_MAX;
80#endif
81}
82
83bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, const char* value) {
84 if (ffStrEqualsIgnCase(key, "-l")) {
85 goto logoType;
86 }
87
88 const char* subKey = ffOptionTestPrefix(key, "logo");
89 if (subKey) {
90 if (subKey[0] == '\0') {
91 logoType:
92 if (value == nullptr) {
93 fprintf(stderr, "Error: usage: %s <none|small|logo-source>\n", key);
94 exit(477);
95 }
96 // this is usually wanted when disabling logo
97 if (ffStrEqualsIgnCase(value, "none")) {
98 options->type = FF_LOGO_TYPE_NONE;
99 } else if (ffStrEqualsIgnCase(value, "small")) {
100 options->type = FF_LOGO_TYPE_SMALL;
101 } else {
102 ffOptionParseString(key, value, &options->source);
103 }
104 } else if (ffStrEqualsIgnCase(subKey, "type")) {
105 options->type = (FFLogoType) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
106 { "auto", FF_LOGO_TYPE_AUTO },
107 { "builtin", FF_LOGO_TYPE_BUILTIN },
108 { "small", FF_LOGO_TYPE_SMALL },
109 { "file", FF_LOGO_TYPE_FILE },
110 { "file-raw", FF_LOGO_TYPE_FILE_RAW },
111 { "data", FF_LOGO_TYPE_DATA },
112 { "data-raw", FF_LOGO_TYPE_DATA_RAW },
113 { "command-raw", FF_LOGO_TYPE_COMMAND_RAW },
114 { "sixel", FF_LOGO_TYPE_IMAGE_SIXEL },
115 { "kitty", FF_LOGO_TYPE_IMAGE_KITTY },
116 { "kitty-direct", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT },
117 { "kitty-icat", FF_LOGO_TYPE_IMAGE_KITTY_ICAT },
118 { "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
119 { "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
120 { "raw", FF_LOGO_TYPE_IMAGE_RAW },
121 { "none", FF_LOGO_TYPE_NONE },
122 {},
123 });
124#if !FF_HAVE_IMAGE_LOGO
125 if (logoTypeIsImage(options->type)) {
126 fputs("Error: Fastfetch was built without image logo support\n", stderr);
127 exit(477);
128 }
129#endif
130 } else if (ffStrStartsWithIgnCase(subKey, "color-") && subKey[6] != '\0' && subKey[7] == '\0') // matches "--logo-color-*"
131 {
132 // Map the number to an array index, so that '1' -> 0, '2' -> 1, etc.
133 int index = (int) subKey[6] - '0' - 1;
134
135 // Match only --logo-color-[1-9]
136 if (index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS) {
137 fprintf(stderr, "Error: invalid --color-[1-9] index: %c\n", key[13]);
138 exit(472);
139 }
140 if (value == nullptr) {
141 fprintf(stderr, "Error: usage: %s <str>\n", key);
142 exit(477);
143 }
144 ffOptionParseColor(value, &options->colors[index]);
145 } else if (ffStrEqualsIgnCase(subKey, "width")) {
146 options->width = ffOptionParseUInt32(key, value);
147 } else if (ffStrEqualsIgnCase(subKey, "height")) {
148 options->height = ffOptionParseUInt32(key, value);
149 } else if (ffStrEqualsIgnCase(subKey, "padding")) {
150 uint32_t padding = ffOptionParseUInt32(key, value);
151 options->paddingLeft = padding;
152 options->paddingRight = padding;
153 } else if (ffStrEqualsIgnCase(subKey, "padding-top")) {
154 options->paddingTop = ffOptionParseUInt32(key, value);
155 } else if (ffStrEqualsIgnCase(subKey, "padding-bottom")) {
156 options->paddingBottom = ffOptionParseUInt32(key, value);
157 } else if (ffStrEqualsIgnCase(subKey, "padding-left")) {
158 options->paddingLeft = ffOptionParseUInt32(key, value);
159 } else if (ffStrEqualsIgnCase(subKey, "padding-right")) {
160 options->paddingRight = ffOptionParseUInt32(key, value);
161 } else if (ffStrEqualsIgnCase(subKey, "print-remaining")) {
162 options->printRemaining = ffOptionParseBoolean(value);
163 } else if (ffStrEqualsIgnCase(subKey, "preserve-aspect-ratio")) {
164 options->preserveAspectRatio = ffOptionParseBoolean(value);
165 } else if (ffStrEqualsIgnCase(subKey, "cache")) {
166 if (value && ffStrEqualsIgnCase(value, "regen")) {
167 options->cache = FF_LOGO_CACHE_REGEN;
168 } else {
169 options->cache = ffOptionParseBoolean(value) ? FF_LOGO_CACHE_ON : FF_LOGO_CACHE_OFF;
170 }
171 } else if (ffStrEqualsIgnCase(subKey, "recache")) {
172 fputs("--logo-recache has been replaced by --logo-cache regen\n", stderr);
173 exit(477);
174 } else if (ffStrEqualsIgnCase(subKey, "separate")) {
175 fputs("--logo-separate has been renamed to --logo-position\n", stderr);
176 exit(477);
177 } else if (ffStrEqualsIgnCase(subKey, "position")) {
178 options->position = (FFLogoPosition) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
179 { "auto", FF_LOGO_POSITION_AUTO },
180 { "left", FF_LOGO_POSITION_LEFT },
181 { "right", FF_LOGO_POSITION_RIGHT },
182 { "top", FF_LOGO_POSITION_TOP },
183 {},
184 });
185 } else if (ffStrEqualsIgnCase(subKey, "animation-frame")) {
186 options->animationFrame = ffOptionParseInt32(key, value);
187 } else {
188 return false;
189 }
190 } else if ((subKey = ffOptionTestPrefix(key, "file"))) {
191 if (subKey[0] == '\0') {
192 ffOptionParseString(key, value, &options->source);
193 options->type = FF_LOGO_TYPE_FILE;
194 } else if (ffStrEqualsIgnCase(subKey, "raw")) {
195 ffOptionParseString(key, value, &options->source);
196 options->type = FF_LOGO_TYPE_FILE_RAW;
197 } else {
198 return false;
199 }
200 } else if ((subKey = ffOptionTestPrefix(key, "data"))) {
201 if (subKey[0] == '\0') {
202 ffOptionParseString(key, value, &options->source);
203 options->type = FF_LOGO_TYPE_DATA;
204 } else if (ffStrEqualsIgnCase(subKey, "raw")) {
205 ffOptionParseString(key, value, &options->source);
206 options->type = FF_LOGO_TYPE_DATA_RAW;
207 } else {
208 return false;
209 }
210 } else if (ffStrEqualsIgnCase(key, "--sixel")) {
211 logoParseImageFlag(options, key, value, FF_LOGO_TYPE_IMAGE_SIXEL);
212 } else if (ffStrEqualsIgnCase(key, "--kitty")) {
213 logoParseImageFlag(options, key, value, FF_LOGO_TYPE_IMAGE_KITTY);
214 } else if (ffStrEqualsIgnCase(key, "--kitty-direct")) {
215 logoParseImageFlag(options, key, value, FF_LOGO_TYPE_IMAGE_KITTY_DIRECT);
216 } else if (ffStrEqualsIgnCase(key, "--kitty-icat")) {
217 logoParseImageFlag(options, key, value, FF_LOGO_TYPE_IMAGE_KITTY_ICAT);
218 } else if (ffStrEqualsIgnCase(key, "--iterm")) {
219 logoParseImageFlag(options, key, value, FF_LOGO_TYPE_IMAGE_ITERM);
220 } else if (ffStrEqualsIgnCase(key, "--raw")) {
221 ffOptionParseString(key, value, &options->source);
222 options->type = FF_LOGO_TYPE_IMAGE_RAW;
223 } else if ((subKey = ffOptionTestPrefix(key, "chafa"))) {
224#if FF_HAVE_CHAFA
225 if (subKey[0] == '\0') {
226 ffOptionParseString(key, value, &options->source);
227 options->type = FF_LOGO_TYPE_IMAGE_CHAFA;
228 } else if (ffStrEqualsIgnCase(subKey, "fg-only")) {
229 options->chafaFgOnly = ffOptionParseBoolean(value);
230 } else if (ffStrEqualsIgnCase(subKey, "symbols")) {
231 ffOptionParseString(key, value, &options->chafaSymbols);
232 } else if (ffStrEqualsIgnCase(subKey, "canvas-mode")) {
233 options->chafaCanvasMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
234 { "TRUECOLOR", 0 },
235 { "INDEXED_256", 1 },
236 { "INDEXED_240", 2 },
237 { "INDEXED_16", 3 },
238 { "FGBG_BGFG", 4 },
239 { "FGBG", 5 },
240 { "INDEXED_8", 6 },
241 { "INDEXED_16_8", 7 },
242 {},
243 });
244 } else if (ffStrEqualsIgnCase(subKey, "color-space")) {
245 options->chafaColorSpace = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
246 { "RGB", 0 },
247 { "DIN99D", 1 },
248 {},
249 });
250 } else if (ffStrEqualsIgnCase(subKey, "dither-mode")) {
251 options->chafaDitherMode = (uint32_t) ffOptionParseEnum(key, value, (FFKeyValuePair[]) {
252 { "NONE", 0 },
253 { "ORDERED", 1 },
254 { "DIFFUSION", 2 },
255 {},
256 });
257 } else {
258 return false;
259 }
260#else
261 fputs("Error: Chafa options are not supported because Fastfetch was built without Chafa support\n", stderr);
262 exit(477);
263#endif
264 } else {
265 return false;
266 }
267
268 return true;
269}
270
271void ffOptionsDestroyLogo(FFOptionsLogo* options) {
272 ffStrbufDestroy(&options->source);
273#if FF_HAVE_CHAFA
274 ffStrbufDestroy(&options->chafaSymbols);
275#endif
276 for (uint8_t i = 0; i < (uint8_t) FASTFETCH_LOGO_MAX_COLORS; ++i) {
277 ffStrbufDestroy(&options->colors[i]);
278 }
279}
280
281const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* root, yyjson_val** pkey) {
282 yyjson_val* object = yyjson_obj_get(root, "logo");
283 if (!object) {
284 return nullptr;
285 }
286 if (yyjson_is_null(object)) {
287 options->type = FF_LOGO_TYPE_NONE;
288 options->paddingTop = 0;
289 options->paddingBottom = 0;
290 options->paddingRight = 0;
291 options->paddingLeft = 0;
292 return nullptr;
293 }
294
295 if (yyjson_is_str(object)) {
296 ffStrbufSetJsonVal(&options->source, object);
297 return nullptr;
298 }
299
300 if (!yyjson_is_obj(object)) {
301 return "Property 'logo' must be an object";
302 }
303
304 yyjson_val *key, *val;
305 size_t idx, max;
306 yyjson_obj_foreach (object, idx, max, key, val) {
307 *pkey = key;
308 if (unsafe_yyjson_equals_str(key, "type")) {
309 int value;
310 const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
311 { "auto", FF_LOGO_TYPE_AUTO },
312 { "builtin", FF_LOGO_TYPE_BUILTIN },
313 { "small", FF_LOGO_TYPE_SMALL },
314 { "file", FF_LOGO_TYPE_FILE },
315 { "file-raw", FF_LOGO_TYPE_FILE_RAW },
316 { "data", FF_LOGO_TYPE_DATA },
317 { "data-raw", FF_LOGO_TYPE_DATA_RAW },
318 { "command-raw", FF_LOGO_TYPE_COMMAND_RAW },
319 { "sixel", FF_LOGO_TYPE_IMAGE_SIXEL },
320 { "kitty", FF_LOGO_TYPE_IMAGE_KITTY },
321 { "kitty-direct", FF_LOGO_TYPE_IMAGE_KITTY_DIRECT },
322 { "kitty-icat", FF_LOGO_TYPE_IMAGE_KITTY_ICAT },
323 { "iterm", FF_LOGO_TYPE_IMAGE_ITERM },
324 { "chafa", FF_LOGO_TYPE_IMAGE_CHAFA },
325 { "raw", FF_LOGO_TYPE_IMAGE_RAW },
326 { "none", FF_LOGO_TYPE_NONE },
327 {},
328 });
329
330 if (error) {
331 return error;
332 }
333#if !FF_HAVE_IMAGE_LOGO
334 if (logoTypeIsImage((FFLogoType) value)) {
335 return "Image logo types are not supported because Fastfetch was built without image logo support";
336 }
337#endif
338 options->type = (FFLogoType) value;
339 continue;
340 } else if (unsafe_yyjson_equals_str(key, "source")) {
341 ffStrbufSetJsonVal(&options->source, val);
342 continue;
343 } else if (unsafe_yyjson_equals_str(key, "color")) {
344 if (!yyjson_is_obj(val)) {
345 return "Property 'color' must be an object";
346 }
347
348 yyjson_val *keyc, *valc;
349 size_t idxc, maxc;
350 yyjson_obj_foreach (val, idxc, maxc, keyc, valc) {
351 uint32_t index = (uint32_t) strtoul(unsafe_yyjson_get_str(keyc), nullptr, 10);
352 if (index < 1 || index > FASTFETCH_LOGO_MAX_COLORS) {
353 return "Keys of property 'color' must be a number between 1 to 9";
354 }
355
356 ffOptionParseColor(yyjson_get_str(valc), &options->colors[index - 1]);
357 }
358 continue;
359 } else if (unsafe_yyjson_equals_str(key, "width")) {
360 if (yyjson_is_null(val)) {
361 options->width = 0;
362 } else {
363 uint32_t value = (uint32_t) yyjson_get_uint(val);
364 if (value == 0) {
365 return "Logo width must be a positive integer";
366 }
367 options->width = value;
368 }
369 continue;
370 } else if (unsafe_yyjson_equals_str(key, "height")) {
371 if (yyjson_is_null(val)) {
372 options->height = 0;
373 } else {
374 uint32_t value = (uint32_t) yyjson_get_uint(val);
375 if (value == 0) {
376 return "Logo height must be a positive integer";
377 }
378 options->height = value;
379 }
380 continue;
381 } else if (unsafe_yyjson_equals_str(key, "padding")) {
382 if (!yyjson_is_obj(val)) {
383 return "Logo padding must be an object";
384 }
385
386#define FF_PARSE_PADDING_POSITON(pos, paddingPos) \
387 yyjson_val* pos = yyjson_obj_get(val, #pos); \
388 if (pos) { \
389 if (!yyjson_is_uint(pos)) \
390 return "Logo padding values must be positive integers"; \
391 options->paddingPos = (uint32_t) yyjson_get_uint(pos); \
392 }
393 FF_PARSE_PADDING_POSITON(left, paddingLeft);
394 FF_PARSE_PADDING_POSITON(top, paddingTop);
395 FF_PARSE_PADDING_POSITON(right, paddingRight);
396 FF_PARSE_PADDING_POSITON(bottom, paddingBottom);
397#undef FF_PARSE_PADDING_POSITON
398 continue;
399 } else if (unsafe_yyjson_equals_str(key, "printRemaining")) {
400 options->printRemaining = yyjson_get_bool(val);
401 continue;
402 } else if (unsafe_yyjson_equals_str(key, "preserveAspectRatio")) {
403 options->preserveAspectRatio = yyjson_get_bool(val);
404 continue;
405 } else if (unsafe_yyjson_equals_str(key, "cache")) {
406 if (yyjson_is_bool(val)) {
407 options->cache = yyjson_get_bool(val) ? FF_LOGO_CACHE_ON : FF_LOGO_CACHE_OFF;
408 } else if (yyjson_is_str(val) && ffStrEqualsIgnCase(yyjson_get_str(val), "regen")) {
409 options->cache = FF_LOGO_CACHE_REGEN;
410 } else {
411 return "Property 'logo.cache' must be a boolean or the string \"regen\"";
412 }
413 continue;
414 } else if (unsafe_yyjson_equals_str(key, "recache")) {
415 // Kept as a named rejection so the message points at the replacement, which the generic
416 // "Unknown logo key" below can not do.
417 return "Property 'logo.recache' has been replaced by 'logo.cache' with the value \"regen\"";
418 } else if (unsafe_yyjson_equals_str(key, "position")) {
419 int value;
420 const char* error = ffJsonConfigParseEnum(val, &value, (FFKeyValuePair[]) {
421 { "auto", FF_LOGO_POSITION_AUTO },
422 { "left", FF_LOGO_POSITION_LEFT },
423 { "top", FF_LOGO_POSITION_TOP },
424 { "right", FF_LOGO_POSITION_RIGHT },
425 {},
426 });
427
428 if (error) {
429 return error;
430 }
431 options->position = (FFLogoPosition) value;
432 continue;
433 } else if (unsafe_yyjson_equals_str(key, "animationFrame")) {
434 if (!yyjson_is_int(val)) {
435 return "Property 'logo.animationFrame' must be an integer";
436 }
437 options->animationFrame = (int32_t) yyjson_get_sint(val);
438 continue;
439 } else if (unsafe_yyjson_equals_str(key, "chafa")) {
440#if FF_HAVE_CHAFA
441 if (!yyjson_is_obj(val)) {
442 return "Chafa config must be an object";
443 }
444
445 yyjson_val* fgOnly = yyjson_obj_get(val, "fgOnly");
446 if (fgOnly) {
447 options->chafaFgOnly = yyjson_get_bool(fgOnly);
448 }
449
450 yyjson_val* symbols = yyjson_obj_get(val, "symbols");
451 if (symbols) {
452 ffStrbufSetJsonVal(&options->chafaSymbols, symbols);
453 }
454
455 yyjson_val* canvasMode = yyjson_obj_get(val, "canvasMode");
456 if (canvasMode) {
457 int value;
458 const char* error = ffJsonConfigParseEnum(canvasMode, &value, (FFKeyValuePair[]) {
459 { "TRUECOLOR", 0 },
460 { "INDEXED_256", 1 },
461 { "INDEXED_240", 2 },
462 { "INDEXED_16", 3 },
463 { "FGBG_BGFG", 4 },
464 { "FGBG", 5 },
465 { "INDEXED_8", 6 },
466 { "INDEXED_16_8", 7 },
467 {},
468 });
469
470 if (error) {
471 return error;
472 }
473 options->chafaCanvasMode = (uint32_t) value;
474 }
475
476 yyjson_val* colorSpace = yyjson_obj_get(val, "colorSpace");
477 if (colorSpace) {
478 int value;
479 const char* error = ffJsonConfigParseEnum(colorSpace, &value, (FFKeyValuePair[]) {
480 { "RGB", 0 },
481 { "DIN99D", 1 },
482 {},
483 });
484
485 if (error) {
486 return error;
487 }
488 options->chafaColorSpace = (uint32_t) value;
489 }
490
491 yyjson_val* ditherMode = yyjson_obj_get(val, "ditherMode");
492 if (ditherMode) {
493 int value;
494 const char* error = ffJsonConfigParseEnum(ditherMode, &value, (FFKeyValuePair[]) {
495 { "NONE", 0 },
496 { "ORDERED", 1 },
497 { "DIFFUSION", 2 },
498 {},
499 });
500
501 if (error) {
502 return error;
503 }
504 options->chafaDitherMode = (uint32_t) value;
505 }
506 continue;
507#else
508 return "Chafa options are not supported because Fastfetch was built without Chafa support";
509#endif
510 } else {
511 return "Unknown logo key";
512 }
513 }
514
515 return nullptr;
516}
517
518[[gnu::cold]]
519void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) {
520 yyjson_mut_doc* doc = data->resultDoc;
521 yyjson_mut_val* obj = yyjson_mut_obj(doc);
522
523 switch (options->type) {
524 case FF_LOGO_TYPE_NONE:
525 yyjson_mut_obj_add_null(doc, doc->root, "logo");
526 return;
527 case FF_LOGO_TYPE_BUILTIN:
528 yyjson_mut_obj_add_str(doc, obj, "type", "builtin");
529 break;
530 case FF_LOGO_TYPE_SMALL:
531 yyjson_mut_obj_add_str(doc, obj, "type", "small");
532 break;
533 case FF_LOGO_TYPE_FILE:
534 yyjson_mut_obj_add_str(doc, obj, "type", "file");
535 break;
536 case FF_LOGO_TYPE_FILE_RAW:
537 yyjson_mut_obj_add_str(doc, obj, "type", "file-raw");
538 break;
539 case FF_LOGO_TYPE_DATA:
540 yyjson_mut_obj_add_str(doc, obj, "type", "data");
541 break;
542 case FF_LOGO_TYPE_DATA_RAW:
543 yyjson_mut_obj_add_str(doc, obj, "type", "data-raw");
544 break;
545 case FF_LOGO_TYPE_COMMAND_RAW:
546 yyjson_mut_obj_add_str(doc, obj, "type", "command-raw");
547 break;
548#if FF_HAVE_IMAGE_LOGO
549 case FF_LOGO_TYPE_IMAGE_SIXEL:
550 yyjson_mut_obj_add_str(doc, obj, "type", "sixel");
551 break;
552 case FF_LOGO_TYPE_IMAGE_KITTY:
553 yyjson_mut_obj_add_str(doc, obj, "type", "kitty");
554 break;
555 case FF_LOGO_TYPE_IMAGE_KITTY_DIRECT:
556 yyjson_mut_obj_add_str(doc, obj, "type", "kitty-direct");
557 break;
558 case FF_LOGO_TYPE_IMAGE_KITTY_ICAT:
559 yyjson_mut_obj_add_str(doc, obj, "type", "kitty-icat");
560 break;
561 case FF_LOGO_TYPE_IMAGE_ITERM:
562 yyjson_mut_obj_add_str(doc, obj, "type", "iterm");
563 break;
564 case FF_LOGO_TYPE_IMAGE_CHAFA:
565 yyjson_mut_obj_add_str(doc, obj, "type", "chafa");
566 break;
567#endif
568 case FF_LOGO_TYPE_IMAGE_RAW:
569 yyjson_mut_obj_add_str(doc, obj, "type", "raw");
570 break;
571 default:
572 yyjson_mut_obj_add_str(doc, obj, "type", "auto");
573 break;
574 }
575
576 yyjson_mut_obj_add_str(doc, obj, "source", options->source.chars);
577
578 {
579 yyjson_mut_val* color = yyjson_mut_obj(doc);
580 for (int i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) {
581 char c = (char) ('1' + i);
582 yyjson_mut_obj_add(color, yyjson_mut_strncpy(doc, &c, 1), yyjson_mut_strbuf(doc, &options->colors[i]));
583 }
584 yyjson_mut_obj_add_val(doc, obj, "color", color);
585 }
586
587 if (options->width == 0) {
588 yyjson_mut_obj_add_null(doc, obj, "width");
589 } else {
590 yyjson_mut_obj_add_uint(doc, obj, "width", options->width);
591 }
592
593 if (options->height == 0) {
594 yyjson_mut_obj_add_null(doc, obj, "height");
595 } else {
596 yyjson_mut_obj_add_uint(doc, obj, "height", options->height);
597 }
598
599 {
600 yyjson_mut_val* padding = yyjson_mut_obj_add_obj(doc, obj, "padding");
601 yyjson_mut_obj_add_uint(doc, padding, "top", options->paddingTop);
602 yyjson_mut_obj_add_uint(doc, padding, "left", options->paddingLeft);
603 yyjson_mut_obj_add_uint(doc, padding, "bottom", options->paddingBottom);
604 yyjson_mut_obj_add_uint(doc, padding, "right", options->paddingRight);
605 }
606
607 yyjson_mut_obj_add_bool(doc, obj, "printRemaining", options->printRemaining);
608
609 yyjson_mut_obj_add_bool(doc, obj, "preserveAspectRatio", options->preserveAspectRatio);
610
611 // Written the way the parser accepts it back: a boolean for on / off, the string for regen
612 switch (options->cache) {
613 case FF_LOGO_CACHE_OFF:
614 yyjson_mut_obj_add_bool(doc, obj, "cache", false);
615 break;
616 case FF_LOGO_CACHE_REGEN:
617 yyjson_mut_obj_add_str(doc, obj, "cache", "regen");
618 break;
619 case FF_LOGO_CACHE_ON:
620 yyjson_mut_obj_add_bool(doc, obj, "cache", true);
621 break;
622 }
623
624 yyjson_mut_obj_add_str(doc, obj, "position", ffLogoPositionToString(options->position));
625
626 yyjson_mut_obj_add_int(doc, obj, "animationFrame", options->animationFrame);
627
628#if FF_HAVE_CHAFA
629 {
630 yyjson_mut_val* chafa = yyjson_mut_obj(doc);
631 yyjson_mut_obj_add_bool(doc, chafa, "fgOnly", options->chafaFgOnly);
632 yyjson_mut_obj_add_strbuf(doc, chafa, "symbols", &options->chafaSymbols);
633 if (options->chafaCanvasMode <= 7) {
634 yyjson_mut_obj_add_str(doc, chafa, "canvasMode", ((const char*[]) {
635 "TRUECOLOR",
636 "INDEXED_256",
637 "INDEXED_240",
638 "INDEXED_16",
639 "FGBG_BGFG",
640 "FGBG",
641 "INDEXED_8",
642 "INDEXED_16_8",
643 })[options->chafaCanvasMode]);
644 }
645 if (options->chafaColorSpace <= 1) {
646 yyjson_mut_obj_add_str(doc, chafa, "colorSpace", ((const char*[]) {
647 "RGB",
648 "DIN99D",
649 })[options->chafaColorSpace]);
650 }
651 if (options->chafaDitherMode <= 2) {
652 yyjson_mut_obj_add_str(doc, chafa, "ditherMode", ((const char*[]) {
653 "NONE",
654 "ORDERED",
655 "DIFFUSION",
656 })[options->chafaDitherMode]);
657 }
658
659 yyjson_mut_obj_add_val(doc, obj, "chafa", chafa);
660 }
661#endif
662
663 yyjson_mut_obj_add_val(doc, doc->root, "logo", obj);
664}