ASCII Art Generator: From Character Mapping to Terminal Art#

You’ve seen those big banner texts in terminals—welcome messages when SSH-ing into servers, npm package logos, CLI tool headers. That’s ASCII Art. Let me walk you through building one.

The Core: Character Mapping Table#

ASCII Art is essentially simulating pixels with monospace characters. Each character is defined as a 5×7 or 5×5 matrix where each position is either a solid block () or empty space.

const ASCII_FONTS = {
  standard: {
    'A': [
      ' █████ ',  // Row 1
      '██   ██',  // Row 2
      '███████',  // Row 3
      '██   ██',  // Row 4
      '██   ██'   // Row 5
    ],
    'B': [
      '██████ ',
      '██   ██',
      '██████ ',
      '██   ██',
      '██████ '
    ],
    // ... other character definitions
  }
}

Key design decisions:

  1. Row-based storage: Each row is a string for easy horizontal concatenation
  2. Uniform height: All characters are 5 rows tall for baseline alignment
  3. Monospace rendering: Uses full-width and spaces for consistent display

Rendering Algorithm: Horizontal Concatenation#

The generation process horizontally concatenates each character’s “pixel matrix”:

function generateAsciiArt(text: string, font: string): string {
  const fontData = ASCII_FONTS[font]
  const upperText = text.toUpperCase()
  const lines: string[] = ['', '', '', '', '']  // 5 empty rows

  for (const char of upperText) {
    const charArt = fontData[char]
    if (charArt) {
      // Append each row of current character to corresponding line
      charArt.forEach((line, i) => {
        lines[i] += line + ' '  // Add space between characters
      })
    } else {
      // Fill unsupported characters with blank
      lines.forEach((_, i) => {
        lines[i] += '     '
      })
    }
  }

  return lines.join('\n')
}

Example with input "AB":

Initial state:
lines = ['', '', '', '', '']

Processing 'A':
lines = [' █████ ', '██   ██', '███████', '██   ██', '██   ██']

Processing 'B':
lines = [' █████ ██████ ', '██   ████   ██', '████████████ ', '██   ████   ██', '██   ████████ ']

Final output:
 █████ ██████
██   ████   ██
████████████
██   ████   ██
██   ████████

Font Design: Pixel to Character Translation#

Designing an ASCII font means “translating” pixel art into character grids. Two common approaches:

1. Manual Drawing#

Draw each character’s 5×5 matrix in a monospace editor:

Designing 'A':

1. Draw pixel map (. = empty, # = solid):
  ###
 #   #
#####
#   #
#   #

2. Convert to characters:
  ███
 █   █
█████
█   █
█   █

3. Store in data structure:
'A': [' ███ ', '█   █', '█████', '█   █', '█   █']

2. Automatic Conversion from Images#

A more advanced approach reads vector fonts or images, then downsamples to 5×5 grids:

function imageToAscii(imageData: ImageData, width: number, height: number): string[] {
  const lines: string[] = []
  const cellWidth = width / 5
  const cellHeight = height / 5

  for (let y = 0; y < 5; y++) {
    let line = ''
    for (let x = 0; x < 5; x++) {
      // Calculate average brightness in current cell
      const brightness = getAverageBrightness(
        imageData,
        Math.floor(x * cellWidth),
        Math.floor(y * cellHeight),
        Math.floor(cellWidth),
        Math.floor(cellHeight)
      )
      line += brightness > 128 ? '█' : ' '
    }
    lines.push(line)
  }
  return lines
}

Multi-Font Support and Extensibility#

Real projects often need multiple font styles. Here’s an extensible font system:

// Font interface definition
interface AsciiFont {
  name: string
  height: number  // Font height in rows
  charset: Record<string, string[]>
}

// Font registry
const FONT_REGISTRY: Record<string, AsciiFont> = {
  standard: {
    name: 'Standard',
    height: 5,
    charset: { /* A-Z, 0-9 */ }
  },
  block: {
    name: 'Block',
    height: 5,
    charset: { /* Different style */ }
  },
  banner: {
    name: 'Banner',
    height: 7,  // Taller font
    charset: { /* Large font */ }
  }
}

// Render function supports dynamic height
function render(text: string, fontName: string): string {
  const font = FONT_REGISTRY[fontName]
  const lines: string[] = Array(font.height).fill('')

  for (const char of text.toUpperCase()) {
    const charArt = font.charset[char]
    if (charArt) {
      charArt.forEach((line, i) => {
        lines[i] += line + ' '
      })
    }
  }

  return lines.join('\n')
}

Performance: Handling Large Text#

Rendering can slow down with long inputs (100+ characters). Optimization strategies:

1. Result Caching#

const cache = new Map<string, string>()

function generateWithCache(text: string, font: string): string {
  const key = `${font}:${text}`
  if (cache.has(key)) {
    return cache.get(key)!
  }
  const result = generateAsciiArt(text, font)
  cache.set(key, result)
  return result
}

2. Virtual Rendering#

For very long outputs, only render the visible area:

function VirtualAsciiOutput({ text, font, viewportHeight }: Props) {
  const fullOutput = useMemo(() => generateAsciiArt(text, font), [text, font])
  const lines = fullOutput.split('\n')

  // Only render visible rows
  const [startLine, setStartLine] = useState(0)
  const visibleLines = lines.slice(startLine, startLine + viewportHeight)

  return (
    <div onScroll={handleScroll}>
      {visibleLines.map((line, i) => (
        <pre key={i}>{line}</pre>
      ))}
    </div>
  )
}

Practical Applications#

1. CLI Tool Banners#

// Print logo when CLI tool starts
console.log(`
 ██████╗ ███████╗ ██████╗ ██████╗ ███████╗
██╔════╝ ██╔════╝██╔═══██╗██╔══██╗██╔════╝
██║  ███╗█████╗  ██║   ██║██║  ██║█████╗
██║   ██║██╔══╝  ██║   ██║██║  ██║██╔══╝
╚██████╔╝███████╗╚██████╔╝██████╔╝███████╗
 ╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝
`)

2. README Headers#

Those big text titles on GitHub project pages—one-click generation with ASCII Art.

3. Code Comments#

/*
 *  ██████╗ ██████╗ ██████╗ ███████╗
 *  ██╔════╝██╔═══██╗██╔══██╗██╔════╝
 *  ██║     ██║   ██║██║  ██║█████╗
 *  ██║     ██║   ██║██║  ██║██╔══╝
 *  ╚██████╗╚██████╔╝██████╔╝███████╗
 *   ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝
 *  Module: Core Algorithm
 */

Edge Cases#

1. Unsupported Characters#

Lowercase letters, special symbols? Convert or show blank:

function renderChar(char: string, font: AsciiFont): string[] {
  const upper = char.toUpperCase()
  return font.charset[upper] || Array(font.height).fill('     ')
}

2. Monospace Font Dependency#

ASCII Art displays correctly only with monospace fonts. In non-monospace environments (some web pages), set <pre> tag to font-family: monospace.

3. Wide Character Issues#

Chinese characters occupy two character widths, causing alignment issues in ASCII Art. Recommend supporting only ASCII character set, or handle specially during rendering.

The Result#

Based on these ideas, I built: ASCII Art Generator

Features:

  • Supports A-Z, 0-9 characters
  • Two font styles (Standard / Block)
  • Real-time preview, one-click copy
  • 20 character limit (prevents performance issues)

The code isn’t complex, but getting character mapping, multi-font support, and edge cases right makes a usable tool.


Related: Image to ASCII | Code Share