How to Create an ASCII Art Generator from Text Input in Batch Script
This guide walks you through building a custom ASCII art generator natively in Windows Batch Script. The program takes a user's typed string and dynamically prints it using large, multi-line ASCII block letters directly in the console.
This project exercises grid-based logic, simulated arrays via environment variables, nested loop processing, and delayed expansion, all within the constraints of the native Command Prompt environment.
The Theory Behind ASCII Character Generation
To render large text in the console, each single character must be represented as a grid of text spanning multiple lines. A typical ASCII font uses a fixed width and height, for example, 5 characters wide by 5 lines tall.
Since Batch has no graphical drawing commands, every printable letter must be predefined inside the script as a set of row strings.
For instance, a 5×5 letter A could look like this:
A
A A
AAAAA
A A
A A
When a user types a word like CAT, the script must:
- Extract
C, look up its first row, and begin building the combined first row. - Append the first row of
A. - Append the first row of
T. - Print the combined first row.
- Repeat for rows 2 through 5.
Creating the Character Dictionary Array
Because each ASCII letter spans multiple lines, we need an array-like mapping system. We define variables representing each letter across multiple indexed rows.
The Wrong Way: Multi-line Literal Sets
A common mistake is attempting to store a multi-line letter inside a single variable using caret (^) line continuation.
Wrong Code Example:
:: Attempting to define a multi-line A in one variable
set "LETTER_A= A ^
A A ^
AAAAA^
A A"
What Happens:
The CMD parser handles whitespace and line continuations unpredictably. When you later try to concatenate LETTER_C beside LETTER_A inside a loop, the embedded line breaks cause output to stack vertically instead of aligning horizontally. The ASCII art is completely destroyed.
The Correct Way: Line-Indexed Variable Maps
The structurally sound method creates a separate variable for every row (1 through 5) of every character. This gives you total control when concatenating rows horizontally.
Correct Code Example:
@echo off
setlocal enabledelayedexpansion
:: Defining the letter 'A' across 5 rows
set "A_1= A "
set "A_2= A A "
set "A_3=AAAAA"
set "A_4=A A"
set "A_5=A A"
:: Defining the letter 'B' across 5 rows
set "B_1=BBBB "
set "B_2=B B"
set "B_3=BBBB "
set "B_4=B B"
set "B_5=BBBB "
:: Defining a space character
set "SPACE_1= "
set "SPACE_2= "
set "SPACE_3= "
set "SPACE_4= "
set "SPACE_5= "
Each variable name follows the pattern {CHAR}_{ROW}, making lookups predictable and mechanical.
Extracting and Normalizing User Input
The script captures a string from the user, then converts it to uppercase so it matches the dictionary variable names exactly.
@echo off
setlocal enabledelayedexpansion
set "INPUT_TEXT="
set /p "INPUT_TEXT=Enter text to convert: "
:: Convert lowercase to uppercase using substitution
for %%a in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do (
call :UPPER_REPLACE %%a
)
goto :DONE_UPPER
:UPPER_REPLACE
set "lower=%1"
for /f "delims=" %%U in ('echo %1^| findstr /i /r "."') do (
rem Direct mapping
)
if "%1"=="a" set "INPUT_TEXT=!INPUT_TEXT:a=A!"
if "%1"=="b" set "INPUT_TEXT=!INPUT_TEXT:b=B!"
if "%1"=="c" set "INPUT_TEXT=!INPUT_TEXT:c=C!"
if "%1"=="d" set "INPUT_TEXT=!INPUT_TEXT:d=D!"
if "%1"=="e" set "INPUT_TEXT=!INPUT_TEXT:e=E!"
if "%1"=="f" set "INPUT_TEXT=!INPUT_TEXT:f=F!"
if "%1"=="g" set "INPUT_TEXT=!INPUT_TEXT:g=G!"
if "%1"=="h" set "INPUT_TEXT=!INPUT_TEXT:h=H!"
if "%1"=="i" set "INPUT_TEXT=!INPUT_TEXT:i=I!"
if "%1"=="j" set "INPUT_TEXT=!INPUT_TEXT:j=J!"
if "%1"=="k" set "INPUT_TEXT=!INPUT_TEXT:k=K!"
if "%1"=="l" set "INPUT_TEXT=!INPUT_TEXT:l=L!"
if "%1"=="m" set "INPUT_TEXT=!INPUT_TEXT:m=M!"
if "%1"=="n" set "INPUT_TEXT=!INPUT_TEXT:n=N!"
if "%1"=="o" set "INPUT_TEXT=!INPUT_TEXT:o=O!"
if "%1"=="p" set "INPUT_TEXT=!INPUT_TEXT:p=P!"
if "%1"=="q" set "INPUT_TEXT=!INPUT_TEXT:q=Q!"
if "%1"=="r" set "INPUT_TEXT=!INPUT_TEXT:r=R!"
if "%1"=="s" set "INPUT_TEXT=!INPUT_TEXT:s=S!"
if "%1"=="t" set "INPUT_TEXT=!INPUT_TEXT:t=T!"
if "%1"=="u" set "INPUT_TEXT=!INPUT_TEXT:u=U!"
if "%1"=="v" set "INPUT_TEXT=!INPUT_TEXT:v=V!"
if "%1"=="w" set "INPUT_TEXT=!INPUT_TEXT:w=W!"
if "%1"=="x" set "INPUT_TEXT=!INPUT_TEXT:x=X!"
if "%1"=="y" set "INPUT_TEXT=!INPUT_TEXT:y=Y!"
if "%1"=="z" set "INPUT_TEXT=!INPUT_TEXT:z=Z!"
goto :eof
:DONE_UPPER
echo %INPUT_TEXT%
Actually, the above is unnecessarily complex. Batch string substitution is case-insensitive on the search side, so the simplest approach is:
:: Convert lowercase to uppercase
for %%a in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do (
set "INPUT_TEXT=!INPUT_TEXT:%%a=%%a!"
)
This works because !INPUT_TEXT:a=A! matches both a and A on the left side and replaces with the uppercase literal on the right.
Because Batch FOR loops treat spaces as delimiters, you cannot iterate over the characters of the input string using a plain FOR. Instead, use a pointer offset with call set to extract characters one at a time by index position.
Implementing the Horizontal Concatenation Engine
This is the core logic. For each of the 5 rows, we iterate through every character in the input string, look up that character's row segment, and append it to a growing output line.
A critical constraint: you cannot use goto inside a for /L body, because goto breaks out of the for context entirely. The solution is to use call to invoke a subroutine that handles the per-row character assembly using its own goto loop.
@echo off
setlocal enabledelayedexpansion
echo.
echo Your ASCII Art:
echo ---------------------------------
for /L %%R in (1,1,5) do (
call :BUILD_ROW %%R
echo !ROW_OUT!
)
goto :CONTINUE
:BUILD_ROW
set "ROW_OUT="
set "ptr=0"
:CHAR_LOOP
call set "char=%%INPUT_TEXT:~!ptr!,1%%"
if "!char!"=="" goto :eof
if "!char!"==" " set "char=SPACE"
call set "segment=%%!char!_%1%%"
if not defined segment set "segment= ??? "
set "ROW_OUT=!ROW_OUT!!segment! "
set /a ptr+=1
goto :CHAR_LOOP
:CONTINUE
When the user enters AB, the processing looks like:
- Row 1: Appends
A_1(A) thenB_1(BBBB). PrintsA BBBB. - Row 2: Appends
A_2(A A) thenB_2(B B). PrintsA A B B. - And so on through Row 5.
Handling Special Characters and Input Validation
If the user passes shell-sensitive characters like &, <, >, or |, the script can break or execute unintended commands. Validate input before processing to allow only letters and spaces.
:: Validate that input contains ONLY letters and spaces
:: findstr returns 0 if the pattern IS found (meaning invalid chars exist)
for /f "delims=" %%V in ('echo "!INPUT_TEXT!"^| findstr /r "[^a-zA-Z ]"') do (
echo [ERROR] Only alphabetical letters and spaces are allowed.
pause
goto :GET_PROMPT
)
However, piping delayed expansion variables through findstr is fragile. A safer approach iterates through each character and checks it against a whitelist:
:VALIDATE
set "valid=1"
set "vptr=0"
:VLOOP
call set "vc=%%INPUT_TEXT:~!vptr!,1%%"
if "!vc!"=="" goto :VDONE
if "!vc!"==" " goto :VNEXT
echo ABCDEFGHIJKLMNOPQRSTUVWXYZ | find /i "!vc!" >nul 2>&1
if errorlevel 1 (
set "valid=0"
goto :VDONE
)
:VNEXT
set /a vptr+=1
goto :VLOOP
:VDONE
if "!valid!"=="0" (
echo [ERROR] String contains invalid characters. Letters and spaces only.
pause
goto :GET_PROMPT
)
Full Working Script
Save the following as banner.bat and run it from any Command Prompt window.
@echo off
setlocal enabledelayedexpansion
title Command Line ASCII Art Generator
:: =======================================
:: 1. Define ASCII Font Arrays (5x5)
:: =======================================
:: Every character must have exactly 5 characters width per row
:: to keep alignment consistent.
set "A_1= A " & set "A_2= A A " & set "A_3=AAAAA" & set "A_4=A A" & set "A_5=A A"
set "B_1=BBBB " & set "B_2=B B" & set "B_3=BBBB " & set "B_4=B B" & set "B_5=BBBB "
set "C_1= CCC " & set "C_2=C C" & set "C_3=C " & set "C_4=C C" & set "C_5= CCC "
set "D_1=DDDD " & set "D_2=D D" & set "D_3=D D" & set "D_4=D D" & set "D_5=DDDD "
set "E_1=EEEEE" & set "E_2=E " & set "E_3=EEE " & set "E_4=E " & set "E_5=EEEEE"
set "F_1=FFFFF" & set "F_2=F " & set "F_3=FFF " & set "F_4=F " & set "F_5=F "
set "G_1= GGG " & set "G_2=G " & set "G_3=G GG" & set "G_4=G G" & set "G_5= GGG "
set "H_1=H H" & set "H_2=H H" & set "H_3=HHHHH" & set "H_4=H H" & set "H_5=H H"
set "I_1=IIIII" & set "I_2= I " & set "I_3= I " & set "I_4= I " & set "I_5=IIIII"
set "J_1=JJJJJ" & set "J_2= J " & set "J_3= J " & set "J_4=J J " & set "J_5= JJ "
set "K_1=K K" & set "K_2=K K " & set "K_3=KKK " & set "K_4=K K " & set "K_5=K K"
set "L_1=L " & set "L_2=L " & set "L_3=L " & set "L_4=L " & set "L_5=LLLLL"
set "M_1=M M" & set "M_2=MM MM" & set "M_3=M M M" & set "M_4=M M" & set "M_5=M M"
set "N_1=N N" & set "N_2=NN N" & set "N_3=N N N" & set "N_4=N NN" & set "N_5=N N"
set "O_1= OOO " & set "O_2=O O" & set "O_3=O O" & set "O_4=O O" & set "O_5= OOO "
set "P_1=PPPP " & set "P_2=P P" & set "P_3=PPPP " & set "P_4=P " & set "P_5=P "
set "Q_1= QQQ " & set "Q_2=Q Q" & set "Q_3=Q Q Q" & set "Q_4=Q Q " & set "Q_5= QQQQ"
set "R_1=RRRR " & set "R_2=R R" & set "R_3=RRRR " & set "R_4=R R " & set "R_5=R RR"
set "S_1= SSS " & set "S_2=S " & set "S_3= SSS " & set "S_4= S" & set "S_5= SSS "
set "T_1=TTTTT" & set "T_2= T " & set "T_3= T " & set "T_4= T " & set "T_5= T "
set "U_1=U U" & set "U_2=U U" & set "U_3=U U" & set "U_4=U U" & set "U_5= UUU "
set "V_1=V V" & set "V_2=V V" & set "V_3=V V" & set "V_4= V V " & set "V_5= V "
set "W_1=W W" & set "W_2=W W" & set "W_3=W W W" & set "W_4=WW WW" & set "W_5=W W"
set "X_1=X X" & set "X_2= X X " & set "X_3= X " & set "X_4= X X " & set "X_5=X X"
set "Y_1=Y Y" & set "Y_2= Y Y " & set "Y_3= Y " & set "Y_4= Y " & set "Y_5= Y "
set "Z_1=ZZZZZ" & set "Z_2= Z " & set "Z_3= Z " & set "Z_4= Z " & set "Z_5=ZZZZZ"
:: Space character (5 wide, blank)
set "SPACE_1= " & set "SPACE_2= " & set "SPACE_3= " & set "SPACE_4= " & set "SPACE_5= "
:: =======================================
:: 2. Prompt for Input
:: =======================================
:GET_PROMPT
cls
echo =======================================
echo BATCH ASCII GENERATOR
echo =======================================
echo.
set "INPUT_TEXT="
set /p "INPUT_TEXT=Enter text to convert (letters and spaces only): "
:: Check for empty input
if not defined INPUT_TEXT goto :GET_PROMPT
:: =======================================
:: 3. Convert to Uppercase
:: =======================================
for %%a in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do (
set "INPUT_TEXT=!INPUT_TEXT:%%a=%%a!"
)
:: =======================================
:: 4. Validate Input (letters and spaces only)
:: =======================================
set "_vptr=0"
:VALIDATE_LOOP
call set "_vc=%%INPUT_TEXT:~!_vptr!,1%%"
if "!_vc!"=="" goto :VALIDATE_PASS
if "!_vc!"==" " goto :VALIDATE_NEXT
set "_found=0"
for %%c in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) do (
if "!_vc!"=="%%c" set "_found=1"
)
if "!_found!"=="0" (
echo.
echo [ERROR] Invalid character "!_vc!" detected. Letters and spaces only.
echo.
pause
goto :GET_PROMPT
)
:VALIDATE_NEXT
set /a _vptr+=1
goto :VALIDATE_LOOP
:VALIDATE_PASS
:: =======================================
:: 5. Build and Display ASCII Art
:: =======================================
echo.
echo GENERATED ART:
echo =======================================================================
for /L %%R in (1,1,5) do (
call :BUILD_ROW %%R
echo(!ROW_OUT!
)
echo =======================================================================
echo.
pause
goto :GET_PROMPT
:: =======================================
:: Subroutine: Assemble one horizontal row
:: =======================================
:BUILD_ROW
set "ROW_OUT="
set "_ptr=0"
:ASSEMBLE_CHAR
call set "_ch=%%INPUT_TEXT:~!_ptr!,1%%"
if "!_ch!"=="" goto :eof
:: Map space to the SPACE variable prefix
if "!_ch!"==" " set "_ch=SPACE"
:: Look up the segment: {CHAR}_{ROW_NUMBER}
call set "_seg=%%!_ch!_%1%%"
:: Fallback for undefined characters
if not defined _seg set "_seg= ? "
:: Append segment plus a single space separator between letters
set "ROW_OUT=!ROW_OUT!!_seg! "
set /a _ptr+=1
goto :ASSEMBLE_CHAR
How It Works Step by Step
-
Font definition. Each letter is stored as five variables (
X_1throughX_5), each exactly 5 characters wide. Consistent width is critical for alignment. -
Input capture.
set /preads the user's string. -
Uppercase conversion. A
forloop appliessetstring substitution for each letter. Batch substitution matches case-insensitively on the left side, so!INPUT_TEXT:a=A!replaces bothaandAwithA. -
Validation. A character-by-character loop checks every character against the 26 uppercase letters and the space character. Any mismatch triggers an error and returns to the prompt.
-
Row assembly. The outer
for /Lloop counts from 1 to 5 (one per row of the font). For each row, it calls:BUILD_ROWwith the row number as%1. -
Character assembly. Inside
:BUILD_ROW, a pointer (_ptr) walks through the input string one character at a time usingcall set "_ch=%%INPUT_TEXT:~!_ptr!,1%%". The double%%andcallare necessary because the pointer value!_ptr!must be resolved first (via delayed expansion), and then the substring operation must be evaluated (via thecallre-parse). -
Segment lookup. The variable name is constructed dynamically: if the character is
Tand the row is3, the script evaluates!T_3!viacall set "_seg=%%!_ch!_%1%%". -
Output. Each assembled row is printed with
echo(!ROW_OUT!. The(immediately afterechopreventsechofrom printing its status (ECHO is on.) whenROW_OUThappens to be empty.
Example Output
Entering HELLO produces:
H H EEEEE L L OOO
H H E L L O O
HHHHH EEE L L O O
H H E L L O O
H H EEEEE LLLLL LLLLL OOO
Extending the Script
You can add digits by following the same pattern:
set "0_1= 000 " & set "0_2=0 0" & set "0_3=0 0" & set "0_4=0 0" & set "0_5= 000 "
set "1_1= 1 " & set "1_2= 11 " & set "1_3= 1 " & set "1_4= 1 " & set "1_5=11111"
Then update the validation loop to also accept digits 0 through 9, and the rest of the engine works without modification because the lookup mechanism is entirely name-driven.
Key Pitfalls and Their Solutions
| Problem | Cause | Solution |
|---|---|---|
ECHO is on. appears in output | echo with empty or whitespace-only argument | Use echo( instead of echo |
| Letters stack vertically instead of side by side | Storing multi-line letters in a single variable | Use one variable per row per character |
goto inside for /L skips remaining iterations | goto breaks out of for context | Use call :subroutine instead |
| Lowercase input not matching variable names | Variable names are uppercase (A_1) but input is lowercase | Convert input to uppercase before lookup |
| Misaligned columns | Inconsistent character widths across letters | Ensure every row of every letter is exactly 5 characters wide |
Script crashes on & or > in input | Shell metacharacters interpreted by CMD | Validate and reject non-alphabetic input before processing |
Conclusion
Building an ASCII art generator in Windows Batch demonstrates how to work around the language's lack of arrays, functions, and string handling by using naming conventions, call-based double expansion, and subroutine loops. The core technique, indexing each row of each character as a separate variable and assembling rows horizontally through a pointer-driven loop, is transferable to any Batch project that needs grid-based or tabular text output.