Skip to main content

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:

  1. Extract C, look up its first row, and begin building the combined first row.
  2. Append the first row of A.
  3. Append the first row of T.
  4. Print the combined first row.
  5. 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.

note

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 ) then B_1 (BBBB ). Prints A BBBB.
  • Row 2: Appends A_2 (A A) then B_2 (B B). Prints A 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

  1. Font definition. Each letter is stored as five variables (X_1 through X_5), each exactly 5 characters wide. Consistent width is critical for alignment.

  2. Input capture. set /p reads the user's string.

  3. Uppercase conversion. A for loop applies set string substitution for each letter. Batch substitution matches case-insensitively on the left side, so !INPUT_TEXT:a=A! replaces both a and A with A.

  4. 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.

  5. Row assembly. The outer for /L loop counts from 1 to 5 (one per row of the font). For each row, it calls :BUILD_ROW with the row number as %1.

  6. Character assembly. Inside :BUILD_ROW, a pointer (_ptr) walks through the input string one character at a time using call set "_ch=%%INPUT_TEXT:~!_ptr!,1%%". The double %% and call are necessary because the pointer value !_ptr! must be resolved first (via delayed expansion), and then the substring operation must be evaluated (via the call re-parse).

  7. Segment lookup. The variable name is constructed dynamically: if the character is T and the row is 3, the script evaluates !T_3! via call set "_seg=%%!_ch!_%1%%".

  8. Output. Each assembled row is printed with echo(!ROW_OUT!. The ( immediately after echo prevents echo from printing its status (ECHO is on.) when ROW_OUT happens 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

ProblemCauseSolution
ECHO is on. appears in outputecho with empty or whitespace-only argumentUse echo( instead of echo
Letters stack vertically instead of side by sideStoring multi-line letters in a single variableUse one variable per row per character
goto inside for /L skips remaining iterationsgoto breaks out of for contextUse call :subroutine instead
Lowercase input not matching variable namesVariable names are uppercase (A_1) but input is lowercaseConvert input to uppercase before lookup
Misaligned columnsInconsistent character widths across lettersEnsure every row of every letter is exactly 5 characters wide
Script crashes on & or > in inputShell metacharacters interpreted by CMDValidate 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.