diff --git a/bin/omarchy b/bin/omarchy index 994172ef..111d214a 100755 --- a/bin/omarchy +++ b/bin/omarchy @@ -27,6 +27,7 @@ declare -A BINARY_TO_KEY declare -A GROUP_DESCRIPTIONS GROUP_DESCRIPTIONS[agent]="AI coding agent usage data" +GROUP_DESCRIPTIONS[ascii]="Text drawn as ASCII art" GROUP_DESCRIPTIONS[audio]="Audio input and output controls" GROUP_DESCRIPTIONS[bar]="Omarchy shell bar layout and settings" GROUP_DESCRIPTIONS[battery]="Battery status helpers" diff --git a/bin/omarchy-ascii b/bin/omarchy-ascii new file mode 100755 index 00000000..166ae92b --- /dev/null +++ b/bin/omarchy-ascii @@ -0,0 +1,1217 @@ +#!/bin/bash + +# omarchy:summary=Render text as ASCII art in the font the Omarchy logo is drawn in +# omarchy:args=[text...] +# omarchy:examples=omarchy ascii Omarchy | omarchy ascii "Hello there" + +set -o pipefail + +usage() { + cat <<'EOF' +Usage: omarchy-ascii [text...] + +Renders text as ASCII art in Delta Corps Priest 1, the FIGlet font the Omarchy +logo is drawn in. Reads the text from stdin when given none. + +Options: + --help Show this help + +Delta Corps Priest 1 draws letters and spaces only. Digits and punctuation have +no glyph in it, so they are skipped. +EOF +} + +words=() + +while (( $# > 0 )); do + case "$1" in + --help) + usage + exit 0 + ;; + --) + shift + words+=("$@") + break + ;; + -?*) + echo "Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + *) + words+=("$1") + ;; + esac + shift +done + +if (( ${#words[@]} > 0 )); then + text="${words[*]}" +elif [[ ! -t 0 ]]; then + text=$(cat) +else + usage >&2 + exit 1 +fi + +if [[ -z ${text//[[:space:]]/} ]]; then + echo "Nothing to render" >&2 + exit 1 +fi + +# The text arrives as awk's input, one record per line, and the font on a +# descriptor of its own. Handing the text to awk as a variable instead would +# read backslash escapes in it as awk's own, and would put a long text through +# the argument list, which has a size limit that a piped one does not. +awk ' +function rtrim(s) { + sub(/ +$/, "", s) + return s +} + +function ltrim(s) { + sub(/^ +/, "", s) + return s +} + +# A single-character replacement that never treats its needle as a pattern, +# because the hardblank is "$" in this font and would anchor a regex instead. +function replace(s, from, to, at, result) { + while ((at = index(s, from)) > 0) { + result = result substr(s, 1, at - 1) to + s = substr(s, at + 1) + } + + return result s +} + +function load_font( line, header, position, code, i) { + while ((getline line < FONT) > 0) { + font_line++ + + if (font_line == 1) { + split(line, header, " ") + hardblank = substr(header[1], length(header[1])) + height = header[2] + comment_lines = header[6] + continue + } + + if (font_line <= comment_lines + 1) { + continue + } + + # Glyphs follow the comments in character order from 32, `height` lines + # each, every line closed by the endmark the first one introduces. + position = font_line - comment_lines - 2 + code = 32 + int(position / height) + if (code > 126) { + continue + } + + if (endmark == "") { + endmark = substr(line, length(line)) + } + + while (length(line) > 0 && substr(line, length(line)) == endmark) { + line = substr(line, 1, length(line) - 1) + } + + for (i = 1; i <= blocks; i++) { + gsub(block[i], stand_in[i], line) + } + + # The hardblank becomes a marker here, while the string is one glyph wide, + # so printing a finished row is a single pass rather than one rescan per + # hardblank in it. + line = replace(line, hardblank, hardblank_mark) + + glyph[code, position % height] = line + if (length(line) > width[code]) { + width[code] = length(line) + } + } + + close(FONT) +} + +function drawable(source, chars, total, i, code) { + total = split(source, chars, "") + for (i = 1; i <= total; i++) { + code = code_of[chars[i]] + if (code != "" && width[code] > 0) { + return 1 + } + } + + return 0 +} + +# Lay one glyph beside what is drawn so far, kerned: slid left until the two +# would touch, which is the smallest run of blanks between them across all rows. +function draw(source, chars, total, i, r, ch, code, piece, glyph_width, out, trail, amount, lead, cut, keep, remaining, fragment, body, pad, line, drew) { + total = split(source, chars, "") + drew = 0 + + for (i = 1; i <= total; i++) { + ch = chars[i] + code = code_of[ch] + + if (code == "" || width[code] == 0) { + if (!(ch in skipped)) { + skipped[ch] = 1 + skipped_list[++skipped_total] = (ch in escaped) ? escaped[ch] : ch + } + + continue + } + + glyph_width = width[code] + for (r = 0; r < height; r++) { + piece[r] = sprintf("%-" glyph_width "s", glyph[code, r]) + } + + if (!drew) { + for (r = 0; r < height; r++) { + out[r] = rtrim(piece[r]) + trail[r] = glyph_width - length(out[r]) + } + + drew = 1 + continue + } + + amount = -1 + for (r = 0; r < height; r++) { + lead = glyph_width - length(ltrim(piece[r])) + if (amount < 0 || trail[r] + lead < amount) { + amount = trail[r] + lead + } + } + + # Every row gives up the same number of columns, taken from its own trailing + # blanks first and from the glyph leading ones once those run out. A row is + # held without its trailing blanks, counted in trail[] instead, so placing a + # glyph costs its own width rather than the width of the art so far -- which + # is what keeps a long line from taking time in the square of its length. + for (r = 0; r < height; r++) { + cut = (amount < trail[r]) ? amount : trail[r] + keep = amount - cut + remaining = trail[r] - cut + fragment = substr(piece[r], keep + 1) + body = rtrim(fragment) + + if (body == "") { + trail[r] = remaining + length(fragment) + } else { + pad = (remaining > 0) ? sprintf("%" remaining "s", "") : "" + out[r] = out[r] pad body + trail[r] = length(fragment) - length(body) + } + } + } + + # A line that drew nothing still occupies its block, the way a blank line + # between two words of art does, so the art keeps the shape of the text. + for (r = 0; r < height; r++) { + line = out[r] + for (i = 1; i <= blocks; i++) { + gsub(stand_in[i], block[i], line) + } + + gsub(hardblank_mark, " ", line) + sub(/ +$/, "", line) + print line + } +} + +BEGIN { + FONT = "/dev/fd/3" + + # The font draws with five block characters, and the layout above is column + # arithmetic. Each one becomes a single byte for the duration and turns back + # at print time, so length() and substr() count columns no matter what the + # locale believes a character is. + blocks = split("█ ▀ ▄ ▌ ▐", block, " ") + for (i = 1; i <= blocks; i++) { + stand_in[i] = sprintf("%c", i) + } + + hardblank_mark = sprintf("%c", blocks + 1) + + for (i = 32; i <= 126; i++) { + code_of[sprintf("%c", i)] = i + } + + # Skipped characters are named on stderr, and a control character named there + # would reach the terminal as a control character. Name those by code. + for (i = 1; i < 32; i++) { + escaped[sprintf("%c", i)] = sprintf("\\x%02x", i) + } + + escaped[sprintf("%c", 127)] = "\\x7f" + + load_font() + + if (height == "") { + print "Could not read the embedded font." >"/dev/stderr" + aborted = 1 + exit 1 + } +} + +{ + source[++source_count] = $0 + if (drawable($0)) { + any_drawable = 1 + } +} + +END { + if (aborted) { + exit 1 + } + + if (!any_drawable) { + print "Delta Corps Priest 1 draws letters and spaces only, and that text has neither." >"/dev/stderr" + exit 1 + } + + for (i = 1; i <= source_count; i++) { + draw(source[i]) + } + + if (skipped_total > 0) { + list = skipped_list[1] + for (i = 2; i <= skipped_total; i++) { + list = list " " skipped_list[i] + } + + print "Skipped, no glyph in Delta Corps Priest 1: " list >"/dev/stderr" + } +} +' 3<<'DELTA_CORPS_PRIEST_1' <<<"$text" +flf2a$ 9 8 19 0 3 0 64 0 +Font Author: CoSMiC cHiLD + +FIGFont created by patorjk.com's FIGFont Editor: http://patorjk.com/figfont-editor +$ $@ +$ $@ +$ $@ +$ $@ +$ $@ +$ $@ +$ $@ +$ $@ +$ $@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ + ▄████████$@ + ███ ███$@ + ███ ███$@ + ███ ███$@ +▀███████████$@ + ███ ███$@ + ███ ███$@ + ███ █▀ $@ + @@ +▀█████████▄ $@ + ███ ███$@ + ███ ███$@ + ▄███▄▄▄██▀ $@ +▀▀███▀▀▀██▄ $@ + ███ ██▄$@ + ███ ███$@ +▄█████████▀ $@ + @@ + ▄████████$@ +███ ███$@ +███ █▀ $@ +███ $@ +███ $@ +███ █▄ $@ +███ ███$@ +████████▀ $@ + @@ +████████▄ $@ +███ ▀███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ▄███$@ +████████▀ $@ + @@ + ▄████████$@ + ███ ███$@ + ███ █▀ $@ + ▄███▄▄▄ $@ +▀▀███▀▀▀ $@ + ███ █▄ $@ + ███ ███$@ + ██████████$@ + @@ + ▄████████$@ + ███ ███$@ + ███ █▀ $@ + ▄███▄▄▄ $@ +▀▀███▀▀▀ $@ + ███ $@ + ███ $@ + ███ $@ + @@ + ▄██████▄ $@ + ███ ███$@ + ███ █▀ $@ + ▄███ $@ +▀▀███ ████▄ $@ + ███ ███$@ + ███ ███$@ + ████████▀ $@ + @@ + ▄█ █▄ $@ + ███ ███ $@ + ███ ███ $@ + ▄███▄▄▄▄███▄▄$@ +▀▀███▀▀▀▀███▀ $@ + ███ ███ $@ + ███ ███ $@ + ███ █▀ $@ + @@ + ▄█ $@ +███ $@ +███▌$@ +███▌$@ +███▌$@ +███ $@ +███ $@ +█▀ $@ + @@ + ▄█$@ + ███$@ + ███$@ + ███$@ + ███$@ + ███$@ + ███$@ +█▄ ▄███$@ +▀▀▀▀▀▀ $@@ + ▄█ ▄█▄$@ + ███ ▄███▀$@ + ███▐██▀ $@ + ▄█████▀ $@ +▀▀█████▄ $@ + ███▐██▄ $@ + ███ ▀███▄$@ + ███ ▀█▀$@ + ▀ $@@ + ▄█ $@ +███ $@ +███ $@ +███ $@ +███ $@ +███ $@ +███▌ ▄$@ +█████▄▄██$@ +▀ $@@ + ▄▄▄▄███▄▄▄▄ $@ + ▄██▀▀▀███▀▀▀██▄$@ + ███ ███ ███$@ + ███ ███ ███$@ + ███ ███ ███$@ + ███ ███ ███$@ + ███ ███ ███$@ + ▀█ ███ █▀ $@ + @@ +███▄▄▄▄ $@ +███▀▀▀██▄$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ + ▀█ █▀ $@ + @@ + ▄██████▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ + ▀██████▀ $@ + @@ + ▄███████▄$@ + ███ ███$@ + ███ ███$@ + ███ ███$@ +▀█████████▀ $@ + ███ $@ + ███ $@ + ▄████▀ $@ + @@ +████████▄ $@ +███ ███ $@ +███ ███ $@ +███ ███ $@ +███ ███ $@ +███ ███ $@ +███ ▀ ███ $@ + ▀██████▀▄█$@ + @@ + ▄████████$@ + ███ ███$@ + ███ ███$@ + ▄███▄▄▄▄██▀$@ +▀▀███▀▀▀▀▀ $@ +▀███████████$@ + ███ ███$@ + ███ ███$@ + ███ ███$@@ + ▄████████$@ + ███ ███$@ + ███ █▀ $@ + ███ $@ +▀███████████$@ + ███$@ + ▄█ ███$@ + ▄████████▀ $@ + @@ + ███ $@ +▀█████████▄$@ + ▀███▀▀██$@ + ███ ▀$@ + ███ $@ + ███ $@ + ███ $@ + ▄████▀ $@ + @@ +███ █▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +████████▀ $@ + @@ + ▄█ █▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ + ▀██████▀ $@ + @@ + ▄█ █▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ▄█▄ ███$@ + ▀███▀███▀ $@ + @@ +▀████ ▐████▀$@ + ███▌ ████▀ $@ + ███ ▐███ $@ + ▀███▄███▀ $@ + ████▀██▄ $@ + ▐███ ▀███ $@ + ▄███ ███▄ $@ +████ ███▄$@ + @@ +▄██ ▄ $@ +███ ██▄$@ +███▄▄▄███$@ +▀▀▀▀▀▀███$@ +▄██ ███$@ +███ ███$@ +███ ███$@ + ▀█████▀ $@ + @@ + ▄███████▄ $@ +██▀ ▄██$@ + ▄███▀$@ + ▀█▀▄███▀▄▄$@ + ▄███▀ ▀$@ +▄███▀ $@ +███▄ ▄█$@ + ▀████████▀$@ + @@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ + ▄████████$@ + ███ ███$@ + ███ ███$@ + ███ ███$@ +▀███████████$@ + ███ ███$@ + ███ ███$@ + ███ █▀ $@ + @@ +▀█████████▄ $@ + ███ ███$@ + ███ ███$@ + ▄███▄▄▄██▀ $@ +▀▀███▀▀▀██▄ $@ + ███ ██▄$@ + ███ ███$@ +▄█████████▀ $@ + @@ + ▄████████$@ +███ ███$@ +███ █▀ $@ +███ $@ +███ $@ +███ █▄ $@ +███ ███$@ +████████▀ $@ + @@ +████████▄ $@ +███ ▀███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ▄███$@ +████████▀ $@ + @@ + ▄████████$@ + ███ ███$@ + ███ █▀ $@ + ▄███▄▄▄ $@ +▀▀███▀▀▀ $@ + ███ █▄ $@ + ███ ███$@ + ██████████$@ + @@ + ▄████████$@ + ███ ███$@ + ███ █▀ $@ + ▄███▄▄▄ $@ +▀▀███▀▀▀ $@ + ███ $@ + ███ $@ + ███ $@ + @@ + ▄██████▄ $@ + ███ ███$@ + ███ █▀ $@ + ▄███ $@ +▀▀███ ████▄ $@ + ███ ███$@ + ███ ███$@ + ████████▀ $@ + @@ + ▄█ █▄ $@ + ███ ███ $@ + ███ ███ $@ + ▄███▄▄▄▄███▄▄$@ +▀▀███▀▀▀▀███▀ $@ + ███ ███ $@ + ███ ███ $@ + ███ █▀ $@ + @@ + ▄█ $@ +███ $@ +███▌$@ +███▌$@ +███▌$@ +███ $@ +███ $@ +█▀ $@ + @@ + ▄█$@ + ███$@ + ███$@ + ███$@ + ███$@ + ███$@ + ███$@ +█▄ ▄███$@ +▀▀▀▀▀▀ $@@ + ▄█ ▄█▄$@ + ███ ▄███▀$@ + ███▐██▀ $@ + ▄█████▀ $@ +▀▀█████▄ $@ + ███▐██▄ $@ + ███ ▀███▄$@ + ███ ▀█▀$@ + ▀ $@@ + ▄█ $@ +███ $@ +███ $@ +███ $@ +███ $@ +███ $@ +███▌ ▄$@ +█████▄▄██$@ +▀ $@@ + ▄▄▄▄███▄▄▄▄ $@ + ▄██▀▀▀███▀▀▀██▄$@ + ███ ███ ███$@ + ███ ███ ███$@ + ███ ███ ███$@ + ███ ███ ███$@ + ███ ███ ███$@ + ▀█ ███ █▀ $@ + @@ +███▄▄▄▄ $@ +███▀▀▀██▄$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ + ▀█ █▀ $@ + @@ + ▄██████▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ + ▀██████▀ $@ + @@ + ▄███████▄$@ + ███ ███$@ + ███ ███$@ + ███ ███$@ +▀█████████▀ $@ + ███ $@ + ███ $@ + ▄████▀ $@ + @@ +████████▄ $@ +███ ███ $@ +███ ███ $@ +███ ███ $@ +███ ███ $@ +███ ███ $@ +███ ▀ ███ $@ + ▀██████▀▄█$@ + @@ + ▄████████$@ + ███ ███$@ + ███ ███$@ + ▄███▄▄▄▄██▀$@ +▀▀███▀▀▀▀▀ $@ +▀███████████$@ + ███ ███$@ + ███ ███$@ + ███ ███$@@ + ▄████████$@ + ███ ███$@ + ███ █▀ $@ + ███ $@ +▀███████████$@ + ███$@ + ▄█ ███$@ + ▄████████▀ $@ + @@ + ███ $@ +▀█████████▄$@ + ▀███▀▀██$@ + ███ ▀$@ + ███ $@ + ███ $@ + ███ $@ + ▄████▀ $@ + @@ +███ █▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +████████▀ $@ + @@ + ▄█ █▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ + ▀██████▀ $@ + @@ + ▄█ █▄ $@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ███$@ +███ ▄█▄ ███$@ + ▀███▀███▀ $@ + @@ +▀████ ▐████▀$@ + ███▌ ████▀ $@ + ███ ▐███ $@ + ▀███▄███▀ $@ + ████▀██▄ $@ + ▐███ ▀███ $@ + ▄███ ███▄ $@ +████ ███▄$@ + @@ +▄██ ▄ $@ +███ ██▄$@ +███▄▄▄███$@ +▀▀▀▀▀▀███$@ +▄██ ███$@ +███ ███$@ +███ ███$@ + ▀█████▀ $@ + @@ + ▄███████▄ $@ +██▀ ▄██$@ + ▄███▀$@ + ▀█▀▄███▀▄▄$@ + ▄███▀ ▀$@ +▄███▀ $@ +███▄ ▄█$@ + ▀████████▀$@ + @@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +@ +@ +@ +@ +@ +@ +@ +@ +@@ +DELTA_CORPS_PRIEST_1 diff --git a/manual/41-branding.md b/manual/41-branding.md index 2cf19001..cdbe828d 100644 --- a/manual/41-branding.md +++ b/manual/41-branding.md @@ -43,3 +43,13 @@ omarchy transcode ascii ~/logo.svg ~/.config/omarchy/branding/screensaver.txt -- ``` It takes `--width` and `--height` in terminal columns and rows, a `--mode` of either `braille` (the default, and much finer) or `block`, a `--threshold` percentage for deciding which pixels count as part of the logo, and `--invert` for when your logo is light on a dark background. If a conversion comes out as a blob, the threshold is usually the knob to turn. + +### Words instead of a logo + +`omarchy ascii` draws text in Delta Corps Priest 1, the FIGlet font the Omarchy wordmark itself is drawn in, so a screensaver can say something rather than show a picture: + +``` +omarchy ascii "Back in five" > ~/.config/omarchy/branding/screensaver.txt +``` + +It takes the text as arguments, or reads it from a pipe when given none. The font carries letters and spaces only — it was drawn without digits or punctuation — so anything else is dropped and named on stderr rather than quietly swallowed. diff --git a/test/shell.d/ascii-test.sh b/test/shell.d/ascii-test.sh new file mode 100755 index 00000000..e30e3ea0 --- /dev/null +++ b/test/shell.d/ascii-test.sh @@ -0,0 +1,147 @@ +#!/bin/bash + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +export PATH="$ROOT/bin:$PATH" + +columns() { + awk 'NR == 1 { print length($0) }' +} + +# The wordmark FIGlet itself draws for this font, so a change to the embedded +# font or to the kerning shows up here rather than in someone's terminal. +expected=$( + cat <<'WORDMARK' + ▄██████▄ ▄▄▄▄███▄▄▄▄ ▄████████ ▄████████ ▄████████ ▄█ █▄ ▄██ ▄ +███ ███ ▄██▀▀▀███▀▀▀██▄ ███ ███ ███ ███ ███ ███ ███ ███ ███ ██▄ +███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███▄▄▄███ +███ ███ ███ ███ ███ ███ ███ ▄███▄▄▄▄██▀ ███ ▄███▄▄▄▄███▄▄ ▀▀▀▀▀▀███ +███ ███ ███ ███ ███ ▀███████████ ▀▀███▀▀▀▀▀ ███ ▀▀███▀▀▀▀███▀ ▄██ ███ +███ ███ ███ ███ ███ ███ ███ ▀███████████ ███ █▄ ███ ███ ███ ███ +███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ + ▀██████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ████████▀ ███ █▀ ▀█████▀ + ███ ███ +WORDMARK +) + +output=$(omarchy-ascii Omarchy) +[[ $output == "$expected" ]] || fail "the wordmark matches the reference rendering" "expected: +$expected +actual: +$output" +pass "the wordmark matches the reference rendering" + +# figlet.js and figlet.c disagree about a first M: the C one trims the column of +# blanks every row of that glyph shares, the JavaScript one keeps it. asciiart.eu +# runs figlet.js, so this pins the leading blanks rather than leaving the next +# reader to "fix" them. +expected_m=$( + cat <<'LETTER_M' + ▄▄▄▄███▄▄▄▄ + ▄██▀▀▀███▀▀▀██▄ + ███ ███ ███ + ███ ███ ███ + ███ ███ ███ + ███ ███ ███ + ███ ███ ███ + ▀█ ███ █▀ + +LETTER_M +) + +output=$(omarchy-ascii M) +[[ $output == "$expected_m" ]] || fail "a leading M keeps the blanks figlet.js gives it" "expected: +$expected_m +actual: +$output" +pass "a leading M keeps the blanks figlet.js gives it" + +output=$(printf 'Omarchy' | omarchy-ascii) +[[ $output == "$expected" ]] || fail "text can arrive on stdin" +pass "text can arrive on stdin" + +# The route is the way a user reaches this, and it dispatches on whether the +# metadata says an argument is required -- so a piped run has to be exercised +# through `omarchy` itself, not just through the binary. +output=$(printf 'Omarchy' | omarchy ascii) +[[ $output == "$expected" ]] || fail "piped text renders through the omarchy route" "got: +$output" +pass "piped text renders through the omarchy route" + +# The block characters are three bytes each, so the column arithmetic has to +# count columns rather than bytes wherever the locale lands. +output=$(LC_ALL=C omarchy-ascii Omarchy) +[[ $output == "$expected" ]] || fail "a byte-only locale draws the same wordmark" +pass "a byte-only locale draws the same wordmark" + +blanks=$(omarchy-ascii Omarchy | grep -c ' $' || true) +[[ $blanks == "0" ]] || fail "no line is padded with trailing blanks" "$blanks lines end in a blank" +pass "no line is padded with trailing blanks" + +# The space is a glyph of five hardblank columns, so it is worth exactly five +# columns of art -- not merely more than none. +tight=$(omarchy-ascii "Hi" | columns) +spaced=$(omarchy-ascii "H i" | columns) +(( tight == 18 )) || fail "Hi is 18 columns wide" "got $tight" +(( spaced == tight + 5 )) || fail "a space between words is five columns" "expected $((tight + 5)), got $spaced" +pass "a space between words is five columns" + +rows=$(omarchy-ascii Hi | wc -l) +(( rows == 9 )) || fail "a block is nine rows" "got $rows" +pass "a block is nine rows" + +# An empty line is a block of its own in figlet, and dropping it would silently +# close up the gap someone put there on purpose. +rows=$(printf 'A\n\nB\n' | omarchy-ascii | wc -l) +(( rows == 27 )) || fail "an empty line still draws its block" "expected 27 rows, got $rows" +pass "an empty line still draws its block" + +# awk reads escapes in a variable it is given on the command line, so text has +# to reach it as input instead: a backslash is a character the font lacks, not +# the start of an escape. +rows=$(omarchy-ascii 'A\nB' 2>/dev/null | wc -l) +(( rows == 9 )) || fail "a backslash in the text is not an escape" "expected 9 rows, got $rows" +pass "a backslash in the text is not an escape" + +# Delta Corps Priest 1 carries letters and spaces only. Dropping the rest in +# silence would leave a version number looking like a bug in the renderer. +status=0 +warning=$(omarchy-ascii "Omarchy 4.0" 2>&1 >/dev/null) || status=$? +(( status == 0 )) || fail "text with unusable characters still draws" "exited $status" +[[ $warning == *"Skipped"* && $warning == *"4"* && $warning == *"."* && $warning == *"0"* ]] || + fail "skipped characters are named on stderr" "got: $warning" +pass "skipped characters are named on stderr" + +output=$(omarchy-ascii "Omarchy 4.0" 2>/dev/null) +[[ $output == "$expected" ]] || fail "the drawable characters still render" +pass "the drawable characters still render" + +# Naming a skipped control character by writing it out would send it to the +# terminal as a control character. +warning=$(printf 'A\001B\n' | omarchy-ascii 2>&1 >/dev/null) +[[ $warning == *'\x01'* ]] || fail "a skipped control character is named by code" "got: $warning" +[[ $warning != *$'\001'* ]] || fail "a skipped control character is not written out raw" +pass "a skipped control character is named by code" + +status=0 +output=$(omarchy-ascii "4.0" 2>/dev/null) || status=$? +(( status == 1 )) || fail "text the font cannot draw at all fails" "exited $status" +[[ -z $output ]] || fail "text the font cannot draw at all prints nothing" +pass "text the font cannot draw at all fails" + +# A mistyped option would otherwise be drawn as art, silently. +status=0 +output=$(omarchy-ascii --width 40 2>&1 >/dev/null) || status=$? +(( status == 1 )) || fail "an unknown option is refused" "exited $status" +[[ $output == *"Unknown option: --width"* ]] || fail "an unknown option is named" "got: $output" +pass "an unknown option is refused" + +output=$(omarchy-ascii -- Hi | columns) +(( output == 18 )) || fail "text after -- is still text" "got $output columns" +pass "text after -- is still text" + +output=$(omarchy-ascii --help) +[[ $output == *"Usage: omarchy-ascii"* ]] || fail "help renders" +pass "help renders"