master ohmu/fastfetch-openbsd / src / options / logo.c
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}