Draw text as ASCII art in the Omarchy logo font (#8037)

* Add omarchy-ascii for drawing text in the logo font

Renders text as ASCII art in Delta Corps Priest 1, the FIGlet font the Omarchy wordmark itself is drawn in, so branding art can be words rather than a picture. The font is embedded in the script and the layout is done in awk, so the command adds nothing to the default package set.

The layout runs on one-byte stand-ins for the five block characters the font draws with. Column arithmetic over the characters themselves counts bytes in one locale and characters in another, and the stand-ins keep length() and substr() counting columns either way.

Delta Corps Priest 1 carries letters and spaces only, and every mirror of it ships the same file with the digit and punctuation glyphs empty. Anything else is dropped and named on stderr, and text with nothing drawable at all exits 1 rather than printing silence.

🤖 Generated by Opus 5 in Claude Code.

* Correct what the renderer did with input it could not draw

The route never ran on piped text. The metadata declared `<text...>` as required, so `omarchy ascii` with nothing on the command line resolved to the router's help while `omarchy-ascii` run directly worked, which is why the tests missed it: they all called the binary. The argument is optional now, and a test goes through the route.

Text reached awk as a command-line variable, where awk reads backslash escapes of its own, so `omarchy ascii 'A\nB'` drew two blocks instead of naming the backslash as a character the font lacks. A text longer than the argument list could not be passed at all. It arrives as awk's input now, with the font on a descriptor of its own.

A line with nothing drawable printed nothing at all, so a blank line between two words closed the gap up rather than keeping it. Every line draws its block now, blank ones included, which is what figlet does with a newline.

Placing a glyph scanned and copied the whole width of the art so far, costing the square of the line's length: four thousand characters took forty-six seconds. A row is now held without its trailing blanks, counted separately instead, so a glyph costs its own width and those four thousand characters take a tenth of a second.

A skipped control character was named on stderr by writing it out, which sends it to the terminal as a control character; those are named by code now. An unknown option was drawn as art rather than refused, so a mistyped `--width 40` quietly rendered the word "width".

figlet.c trims the column of blanks that every row of an `M` shares when it is the first glyph on a line, and figlet.js keeps it. asciiart.eu runs figlet.js, so the rendering follows figlet.js and a test pins that `M`, because the wordmark alone does not catch the difference and the next reader would have no way to tell the choice from an oversight.

🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Codex XHigh <codex@openai.com>

---------

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Codex XHigh <codex@openai.com>
This commit is contained in:
Omarchybot
2026-08-24 17:58:52 +02:00
committed by GitHub
co-authored by Codex XHigh David Heinemeier Hansson
parent d99d4fc6de
commit 4baae6bf2a
4 changed files with 1375 additions and 0 deletions
+1
View File
@@ -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"
+1217
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -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.
+147
View File
@@ -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"