From 3f0a93d057b182ea88c6be567b644dd1c93a2d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 22 May 2026 20:21:07 +0800 Subject: [PATCH] Logo: reworks `position: top`; adds a new option `--logo-padding-bottom` --- doc/json_schema.json | 5 ++ src/logo/logo.c | 209 ++----------------------------------------- src/options/logo.c | 6 ++ src/options/logo.h | 1 + 4 files changed, 21 insertions(+), 200 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index f8198587d..fd463b79e 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -731,6 +731,11 @@ "description": "Top padding of the logo", "minimum": 0 }, + "bottom": { + "type": "integer", + "description": "Bottom padding of the logo; only supported when position is `top`", + "minimum": 0 + }, "left": { "type": "integer", "description": "Left padding of the logo", diff --git a/src/logo/logo.c b/src/logo/logo.c index 246ba458d..afd08d817 100644 --- a/src/logo/logo.c +++ b/src/logo/logo.c @@ -275,214 +275,23 @@ static bool ffLogoPrintCharsRaw(const char* data, size_t length, bool printError return true; } -// If result is NULL, calculate logo width -// Returns logo height -static uint32_t logoAppendChars(const char* data, bool doColorReplacement, FFstrbuf* result) { - FFOptionsLogo* options = &instance.config.logo; - uint32_t currentlineLength = options->type == FF_LOGO_TYPE_IMAGE_CHAFA ? 0 : options->width; // For chafa, unit of options->width is pixels - uint32_t logoHeight = 0; - - if (result) { - if (options->position != FF_LOGO_POSITION_RIGHT) { - ffStrbufAppendNC(result, options->paddingLeft, ' '); - } else { - ffStrbufAppendF(result, "\e[9999999C\e[%dD", options->paddingRight + instance.state.logoWidth); - } - } - - while (*data != '\0') { - // We are at the end of a line. Print paddings and update max line length - if (*data == '\n' || (*data == '\r' && *(data + 1) == '\n')) { - // We have \r\n, skip the \r - if (*data == '\r') { - ++data; - } - - if (result) { - ffStrbufAppendC(result, '\n'); - } - ++data; - - if (result) { - if (options->position != FF_LOGO_POSITION_RIGHT) { - ffStrbufAppendNC(result, options->paddingLeft, ' '); - } else { - ffStrbufAppendF(result, "\e[9999999C\e[%dD", options->paddingRight + instance.state.logoWidth); - } - } - - if (currentlineLength > instance.state.logoWidth) { - instance.state.logoWidth = currentlineLength; - } - - currentlineLength = 0; - ++logoHeight; - continue; - } - - // Always print tabs as 4 spaces, to have consistent spacing - if (*data == '\t') { - if (result) { - ffStrbufAppendNC(result, 4, ' '); - } - ++data; - continue; - } - - // We have an escape sequence directly as bytes. We print it, but don't increase the line length - if (*data == '\e' && *(data + 1) == '[') { - const char* start = data; - - if (result) { - ffStrbufAppendS(result, "\e["); - } - data += 2; - - while (ffCharIsDigit(*data) || *data == ';') { - if (result) { - ffStrbufAppendC(result, *data); // number - } - ++data; - } - - // We have a valid control sequence, print it and continue with next char - if (isascii(*data)) { - if (result) { - ffStrbufAppendC(result, *data); // single letter, end of control sequence - } - ++data; - continue; - } - - // Invalid control sequence, try to get most accurate length - currentlineLength += (uint32_t) (data - start - 1); //-1 for \033 which for sure doesn't take any space - - // Don't continue here, print the char after the letters with the unicode printing - } - - // We have a fastfetch color placeholder. Replace it with the esacape sequence, don't increase the line length - if (doColorReplacement && *data == '$') { - ++data; - - // If we have $$, or $\0, print it as single $ - if (*data == '$' || *data == '\0') { - if (result) { - ffStrbufAppendC(result, '$'); - } - ++currentlineLength; - ++data; - continue; - } - - if (!instance.config.display.pipe) { - // Map the number to an array index, so that '1' -> 0, '2' -> 1, etc. - int index = *data - '1'; - - // If the index is valid, print the color. Otherwise continue as normal - if (index < 0 || index >= FASTFETCH_LOGO_MAX_COLORS) { - if (result) { - ffStrbufAppendC(result, '$'); - } - ++currentlineLength; - // Don't continue here, we want to print the current char as unicode - } else { - if (result) { - ffStrbufAppendF(result, "\e[%sm", options->colors[index].chars); - } - ++data; - continue; - } - } else { - ++data; - continue; - } - } - - // Do the printing, respecting unicode - - ++currentlineLength; - - int codepoint = (unsigned char) *data; - uint8_t bytes; - - if (codepoint <= 127) { - bytes = 1; - } else if ((codepoint & 0xE0) == 0xC0) { - bytes = 2; - } else if ((codepoint & 0xF0) == 0xE0) { - bytes = 3; - } else if ((codepoint & 0xF8) == 0xF0) { - bytes = 4; - } else { - bytes = 1; // Invalid utf8, print it as is, byte by byte - } - - for (uint8_t i = 0; i < bytes; ++i) { - if (*data == '\0') { - break; - } - - if (result) { - ffStrbufAppendC(result, *data); - } - ++data; - } - } - // Happens if the last line is the longest - if (currentlineLength > instance.state.logoWidth) { - instance.state.logoWidth = currentlineLength; - } - - return options->type != FF_LOGO_TYPE_IMAGE_CHAFA && options->height > logoHeight ? options->height : logoHeight; -} - void ffLogoPrintChars(const char* data, bool doColorReplacement) { FFOptionsLogo* options = &instance.config.logo; - if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) { - logoLineCacheBuild(data, doColorReplacement); + logoLineCacheBuild(data, doColorReplacement); + + if (options->position != FF_LOGO_POSITION_TOP) { return; } - if (options->position == FF_LOGO_POSITION_RIGHT) { - logoAppendChars(data, doColorReplacement, NULL); + FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); + FF_LIST_FOR_EACH (FFLogoCachedLine, line, instance.state.logoLineCache.lines) { + ffStrbufAppend(&result, &line->chars); + ffStrbufAppendC(&result, '\n'); } - - FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(2048); - - if (!instance.config.display.pipe && instance.config.display.brightColor) { - ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_BOLT); - } - - ffStrbufAppendNC(&result, options->paddingTop, '\n'); - - // Use logoColor[0] as the default color - if (doColorReplacement && !instance.config.display.pipe) { - ffStrbufAppendF(&result, "\e[%sm", options->colors[0].chars); - } - - instance.state.logoHeight = options->paddingTop + logoAppendChars(data, doColorReplacement, &result); - - if (!instance.config.display.pipe) { - ffStrbufAppendS(&result, FASTFETCH_TEXT_MODIFIER_RESET); - } - - if (options->position == FF_LOGO_POSITION_LEFT) { - instance.state.logoWidth += options->paddingLeft + options->paddingRight; - - // Go to the leftmost position and go up the height - ffStrbufAppendF(&result, "\e[1G\e[%uA", instance.state.logoHeight); - } else if (options->position == FF_LOGO_POSITION_RIGHT) { - instance.state.logoWidth = 0; - - // Go to the leftmost position and go up the height - ffStrbufAppendF(&result, "\e[1G\e[%uA", instance.state.logoHeight); - } else if (options->position == FF_LOGO_POSITION_TOP) { - instance.state.logoWidth = instance.state.logoHeight = 0; - ffStrbufAppendNC(&result, options->paddingRight, '\n'); - } - + ffStrbufAppendNC(&result, options->paddingBottom, '\n'); ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &result); + instance.state.logoWidth = instance.state.logoHeight = 0; } static void logoApplyColors(const FFlogo* logo, bool replacement) { diff --git a/src/options/logo.c b/src/options/logo.c index d04f1e2fb..804e0b73c 100644 --- a/src/options/logo.c +++ b/src/options/logo.c @@ -12,6 +12,7 @@ void ffOptionsInitLogo(FFOptionsLogo* options) { options->width = 0; options->height = 0; // preserve aspect ratio options->paddingTop = 0; + options->paddingBottom = 0; options->paddingLeft = 0; options->paddingRight = 4; options->printRemaining = true; @@ -92,6 +93,8 @@ bool ffOptionsParseLogoCommandLine(FFOptionsLogo* options, const char* key, cons options->paddingRight = padding; } else if (ffStrEqualsIgnCase(subKey, "padding-top")) { options->paddingTop = ffOptionParseUInt32(key, value); + } else if (ffStrEqualsIgnCase(subKey, "padding-bottom")) { + options->paddingBottom = ffOptionParseUInt32(key, value); } else if (ffStrEqualsIgnCase(subKey, "padding-left")) { options->paddingLeft = ffOptionParseUInt32(key, value); } else if (ffStrEqualsIgnCase(subKey, "padding-right")) { @@ -212,6 +215,7 @@ const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* roo if (yyjson_is_null(object)) { options->type = FF_LOGO_TYPE_NONE; options->paddingTop = 0; + options->paddingBottom = 0; options->paddingRight = 0; options->paddingLeft = 0; return NULL; @@ -312,6 +316,7 @@ const char* ffOptionsParseLogoJsonConfig(FFOptionsLogo* options, yyjson_val* roo FF_PARSE_PADDING_POSITON(left, paddingLeft); FF_PARSE_PADDING_POSITON(top, paddingTop); FF_PARSE_PADDING_POSITON(right, paddingRight); + FF_PARSE_PADDING_POSITON(bottom, paddingBottom); #undef FF_PARSE_PADDING_POSITON continue; } else if (unsafe_yyjson_equals_str(key, "printRemaining")) { @@ -494,6 +499,7 @@ void ffOptionsGenerateLogoJsonConfig(FFdata* data, FFOptionsLogo* options) { yyjson_mut_val* padding = yyjson_mut_obj_add_obj(doc, obj, "padding"); yyjson_mut_obj_add_uint(doc, padding, "top", options->paddingTop); yyjson_mut_obj_add_uint(doc, padding, "left", options->paddingLeft); + yyjson_mut_obj_add_uint(doc, padding, "bottom", options->paddingBottom); yyjson_mut_obj_add_uint(doc, padding, "right", options->paddingRight); } diff --git a/src/options/logo.h b/src/options/logo.h index 62e47ea81..21b63fff7 100644 --- a/src/options/logo.h +++ b/src/options/logo.h @@ -40,6 +40,7 @@ typedef struct FFOptionsLogo { uint32_t paddingTop; uint32_t paddingLeft; uint32_t paddingRight; + uint32_t paddingBottom; bool printRemaining; bool preserveAspectRatio; bool recache;