mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-11-04 22:14:20 +01:00
56f4dc00a2
The builtin logo, or a text file in logo syntax, drawn as an extruded slab: the front is the art as it is, the back is the art mirrored, and the side walls are shaded with block glyphs. Rendering is pure: no terminal, no input, no timer. `--logo-type spin` hands every frame to the normal logo pipeline, so the modules are laid out beside it exactly as they are for any other type, and the first frame without --watch is byte-identical to the same art printed as a builtin logo. The rotation is driven by the `--watch` loop: `ffSpinTick()` runs once per refresh and `ffLogoPrint()` is called only when it reports a step, so the logo and the modules can never drift apart, and a build that never uses the type costs one comparison per refresh. Speed is set either as degrees per frame (--logo-spin-speed-per-frame, default 10) or as seconds per revolution (--logo-spin-speed-per-round, read from the clock and winning over the former when both are given). Both take negative values to turn the other way, and --logo-spin-angle picks the frame to start from.
225 lines
8.0 KiB
C
225 lines
8.0 KiB
C
#include "fastfetch.h"
|
|
#include "logo/logo.h"
|
|
#include "logo/spin/render.h"
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifndef M_PI
|
|
#define M_PI 3.14159265358979323846
|
|
#endif
|
|
|
|
// Unlike assert(), this also checks in release builds.
|
|
#define VERIFY(expression) \
|
|
do { \
|
|
if (!(expression)) { \
|
|
fprintf(stderr, "spin test failed [%d]: %s\n", __LINE__, #expression); \
|
|
exit(1); \
|
|
} \
|
|
} while (0)
|
|
|
|
static bool isWallGlyph(uint32_t glyph) {
|
|
return glyph >= 0x2591 && glyph <= 0x2593 ? true : glyph == 0x2588; // ░▒▓█
|
|
}
|
|
|
|
static void testRotation(void) {
|
|
const float angles[] = { 0.0f, 0.25f, (float) M_PI / 2.0f, (float) M_PI, 5.0f };
|
|
const float tilts[] = { 0.0f, 0.2f, -0.4f, 1.0f };
|
|
for (uint32_t i = 0; i < ARRAY_SIZE(angles); ++i) {
|
|
for (uint32_t j = 0; j < ARRAY_SIZE(tilts); ++j) {
|
|
float rotation[3][3], inverse[3][3];
|
|
ffSpinRotationMatrix(angles[i], tilts[j], rotation, inverse);
|
|
VERIFY(ffSpinRotationIsOrthonormal(rotation, 1e-4f));
|
|
for (uint32_t r = 0; r < 3; ++r) {
|
|
for (uint32_t c = 0; c < 3; ++c) {
|
|
VERIFY(fabsf(rotation[r][c] - inverse[c][r]) < 1e-5f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void testParse(void) {
|
|
FFstrbuf colors[2];
|
|
ffStrbufInitStatic(&colors[0], "31");
|
|
ffStrbufInitStatic(&colors[1], "1;34");
|
|
|
|
FFSpinArt art;
|
|
// Leading blank rows and columns are cropped; `$$` is a literal dollar.
|
|
VERIFY(ffSpinArtParse(&art, "\n $1ab\n $2c$$\xe2\x96\x88\n", colors, 2));
|
|
VERIFY(art.width == 3 && art.height == 2);
|
|
VERIFY(art.glyphs[0] == 'a' && art.colors[0] == 1);
|
|
VERIFY(art.glyphs[1] == 'b' && art.colors[1] == 1);
|
|
VERIFY(art.glyphs[2] == 0);
|
|
VERIFY(art.glyphs[3] == 'c' && art.colors[3] == 2);
|
|
VERIFY(art.glyphs[4] == '$');
|
|
VERIFY(art.glyphs[5] == 0x2588);
|
|
VERIFY(ffStrbufEqualS(&art.palette[2], "1;34"));
|
|
ffSpinArtDestroy(&art);
|
|
|
|
// Raw SGR escapes in custom logo files get their own palette slots.
|
|
VERIFY(ffSpinArtParse(&art, "\e[38;5;208mx\e[0my", nullptr, 0));
|
|
VERIFY(art.width == 2 && art.height == 1);
|
|
VERIFY(art.colors[0] >= FF_SPIN_PALETTE_LOGO_SLOTS);
|
|
VERIFY(ffStrbufEqualS(&art.palette[art.colors[0]], "38;5;208"));
|
|
VERIFY(art.colors[1] == 0);
|
|
ffSpinArtDestroy(&art);
|
|
|
|
VERIFY(!ffSpinArtParse(&art, " \n \t \n", nullptr, 0));
|
|
ffSpinArtDestroy(&art);
|
|
VERIFY(!ffSpinArtParse(&art, "", nullptr, 0));
|
|
ffSpinArtDestroy(&art);
|
|
}
|
|
|
|
static void testMirror(void) {
|
|
VERIFY(ffSpinMirrorGlyph('/') == '\\');
|
|
VERIFY(ffSpinMirrorGlyph('\\') == '/');
|
|
VERIFY(ffSpinMirrorGlyph('(') == ')');
|
|
VERIFY(ffSpinMirrorGlyph('o') == 'o');
|
|
VERIFY(ffSpinMirrorGlyph(0x2801) == 0x2808); // braille dot 1 -> dot 4
|
|
VERIFY(ffSpinMirrorGlyph(0x2840) == 0x2880); // braille dot 7 -> dot 8
|
|
for (uint32_t glyph = 0x2800; glyph <= 0x28FF; ++glyph) {
|
|
VERIFY(ffSpinMirrorGlyph(ffSpinMirrorGlyph(glyph)) == glyph);
|
|
}
|
|
}
|
|
|
|
static const char testLogo[] =
|
|
"$1 /\\\n"
|
|
"$1 / \\\n"
|
|
"$1 /\\ \\\n"
|
|
"$2 / __ \\\n"
|
|
"$2/__/ \\__\\";
|
|
|
|
static void testRenderFaces(void) {
|
|
FFstrbuf colors[2];
|
|
ffStrbufInitStatic(&colors[0], "36");
|
|
ffStrbufInitStatic(&colors[1], "34");
|
|
FFSpinArt art;
|
|
VERIFY(ffSpinArtParse(&art, testLogo, colors, 2));
|
|
|
|
uint32_t width, height;
|
|
ffSpinFrameSize(&art, 0.0f, &width, &height);
|
|
VERIFY(width >= art.width && height >= art.height);
|
|
VERIFY((width - art.width) % 2 == 0 && (height - art.height) % 2 == 0);
|
|
uint32_t left = (width - art.width) / 2, top = (height - art.height) / 2;
|
|
|
|
FFSpinFrame frame;
|
|
VERIFY(ffSpinFrameInit(&frame, width, height));
|
|
|
|
// Face-on, every cell of the art shows up unchanged at its place.
|
|
ffSpinRenderArt(&art, 0.0f, 0.0f, &frame);
|
|
for (uint32_t y = 0; y < art.height; ++y) {
|
|
for (uint32_t x = 0; x < art.width; ++x) {
|
|
uint32_t glyph = art.glyphs[y * art.width + x];
|
|
if (!glyph) continue;
|
|
size_t index = (size_t) (top + y) * width + left + x;
|
|
VERIFY(frame.glyphs[index] == glyph);
|
|
VERIFY(frame.colors[index] == art.colors[y * art.width + x]);
|
|
}
|
|
}
|
|
|
|
// Turned around, the back face shows the art mirrored, glyphs included.
|
|
ffSpinRenderArt(&art, (float) M_PI, 0.0f, &frame);
|
|
for (uint32_t y = 0; y < art.height; ++y) {
|
|
for (uint32_t x = 0; x < art.width; ++x) {
|
|
uint32_t glyph = art.glyphs[y * art.width + x];
|
|
if (!glyph) continue;
|
|
size_t index = (size_t) (top + y) * width + left + (art.width - 1 - x);
|
|
VERIFY(frame.glyphs[index] == ffSpinMirrorGlyph(glyph));
|
|
}
|
|
}
|
|
|
|
// Edge-on, only a thin band of edge shading is left.
|
|
ffSpinRenderArt(&art, (float) M_PI / 2.0f, 0.0f, &frame);
|
|
uint32_t usedColumns = 0;
|
|
for (uint32_t x = 0; x < width; ++x) {
|
|
bool used = false;
|
|
for (uint32_t y = 0; y < height; ++y) {
|
|
uint32_t glyph = frame.glyphs[y * width + x];
|
|
if (glyph) {
|
|
used = true;
|
|
VERIFY(isWallGlyph(glyph));
|
|
}
|
|
}
|
|
usedColumns += used;
|
|
}
|
|
VERIFY(usedColumns > 0 && usedColumns <= 4);
|
|
|
|
ffSpinFrameDestroy(&frame);
|
|
ffSpinArtDestroy(&art);
|
|
}
|
|
|
|
// Renders into a frame one cell larger on every side than ffSpinFrameSize
|
|
// asks for; the extra ring must stay empty at every angle.
|
|
static void verifyFits(const FFSpinArt* art, float tilt) {
|
|
uint32_t width, height;
|
|
ffSpinFrameSize(art, tilt, &width, &height);
|
|
FFSpinFrame frame;
|
|
VERIFY(ffSpinFrameInit(&frame, width + 2, height + 2));
|
|
for (uint32_t step = 0; step < 48; ++step) {
|
|
ffSpinRenderArt(art, (float) step * 2.0f * (float) M_PI / 48.0f, tilt, &frame);
|
|
for (uint32_t x = 0; x < frame.width; ++x) {
|
|
VERIFY(frame.glyphs[x] == 0);
|
|
VERIFY(frame.glyphs[(size_t) (frame.height - 1) * frame.width + x] == 0);
|
|
}
|
|
for (uint32_t y = 0; y < frame.height; ++y) {
|
|
VERIFY(frame.glyphs[(size_t) y * frame.width] == 0);
|
|
VERIFY(frame.glyphs[(size_t) y * frame.width + frame.width - 1] == 0);
|
|
}
|
|
}
|
|
ffSpinFrameDestroy(&frame);
|
|
}
|
|
|
|
static void testBuiltinLogos(void) {
|
|
uint32_t count = 0;
|
|
for (uint32_t letter = 0; letter < 26; ++letter) {
|
|
for (const FFlogo* logo = ffLogoBuiltins[letter]; *logo->names; ++logo) {
|
|
FFSpinArt art;
|
|
VERIFY(ffSpinArtParse(&art, logo->lines, nullptr, 0));
|
|
VERIFY(art.width > 0 && art.height > 0);
|
|
verifyFits(&art, 0.0f);
|
|
if (count % 16 == 0) {
|
|
verifyFits(&art, 0.4f);
|
|
}
|
|
ffSpinArtDestroy(&art);
|
|
++count;
|
|
}
|
|
}
|
|
VERIFY(count > 400);
|
|
}
|
|
|
|
static void testAnsiOutput(void) {
|
|
FFstrbuf colors[1];
|
|
ffStrbufInitStatic(&colors[0], "35");
|
|
FFSpinArt art;
|
|
VERIFY(ffSpinArtParse(&art, "a\xe2\x96\x88 b", colors, 1));
|
|
FFSpinFrame frame;
|
|
VERIFY(ffSpinFrameInit(&frame, art.width, art.height));
|
|
ffSpinFramePutArt(&frame, &art);
|
|
|
|
FF_STRBUF_AUTO_DESTROY plain = ffStrbufCreate();
|
|
ffSpinFrameToAnsi(&frame, &art, false, false, true, &plain);
|
|
VERIFY(ffStrbufEqualS(&plain, "a\xe2\x96\x88 b\n"));
|
|
|
|
FF_STRBUF_AUTO_DESTROY colored = ffStrbufCreate();
|
|
ffSpinFrameToAnsi(&frame, &art, true, true, true, &colored);
|
|
VERIFY(strstr(colored.chars, "\e[0;1;35ma") != nullptr);
|
|
VERIFY(strstr(colored.chars, "\e[0m\n") != nullptr);
|
|
|
|
ffSpinFrameDestroy(&frame);
|
|
ffSpinArtDestroy(&art);
|
|
}
|
|
|
|
int main(void) {
|
|
testRotation();
|
|
testParse();
|
|
testMirror();
|
|
testRenderFaces();
|
|
testBuiltinLogos();
|
|
testAnsiOutput();
|
|
puts("spin tests passed");
|
|
return 0;
|
|
}
|