# Charsets

The ten built-in glyph sets, how Braille and quadrants pack several samples into one cell, and how to bring your own ramp.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The asciicn agent skill, for building any ASCII effect on the engine, is at [/.well-known/agent-skills/asciicn/SKILL.md](/.well-known/agent-skills/asciicn/SKILL.md).

A charset is the set of glyphs a renderer draws tones with. Every component that draws a field takes a `charset` prop, and the same picture reads very differently in each one:



```tsx
<AsciiImage src="/portrait.jpg" charset="braille" />
```

## Built-in charsets [#built-in-charsets]

| `charset`   | Glyphs                                 | Samples per cell | Good for                                          |
| ----------- | -------------------------------------- | ---------------- | ------------------------------------------------- |
| `standard`  | `" .:-=+*#%@"`                         | 1                | Photos and anything that should read as ASCII art |
| `dense`     | 70 glyphs, `.` to `$`                  | 1                | Smooth tone on large grids                        |
| `blocks`    | `" ░▒▓█"`                              | 1                | Bold fills, the ANSI-art look                     |
| `quadrants` | `▘▝▀▖▌▞▛▗▚▐▜▄▙▟█`                      | 2×2              | Blocky shapes with twice the detail               |
| `braille`   | 256 dot patterns, `⠁` to `⣿`           | 2×4              | Fine lines, plots, wireframes                     |
| `cp437`     | IBM PC symbols, `·` to `█`             | 1                | DOS-era texture                                   |
| `petscii`   | Commodore lines and blocks             | 1                | Retro computer screens                            |
| `katakana`  | Half-width katakana and digits         | 1                | Signage, glyph streams                            |
| `dots`      | `" ·∙•⦁●"`, five sizes                 | 1                | Halftone print and painted scenes                 |
| `edge`      | `- / \ \|` on edges, `standard` inside | 1                | Line drawings and outlines                        |

Ramps are ordered by how much ink each glyph puts on the cell in a typical monospace font, so they read correctly even where coverage can't be measured, such as in an `AsciiPre` rendered on the server. `CHARSET_NAMES` lists the ids in picker order and `CHARSETS[id]` holds each set's label, kind, glyphs and sample grid.

## Halftone dots [#halftone-dots]

`dots` draws five round dots after the empty cell, from geometry rather than the font, so they are round in every font. Their radii are 0.07, 0.13, 0.24, 0.33 and 0.42 of the cell. The two smallest are specks: a dark sky drawn with them reads as quiet air, and the large dots stay for the light. With `dither` set, a tone between two sizes mixes them in a fixed pattern, so gradients don't band.

Set `square` with `dots`. Default cells are about twice as tall as wide, so the rows of dots sit twice as far apart as the columns and a picture reads as stripes. Square cells lay the dots on an even lattice. See [Cell metrics](/docs/engine/cell-metrics#square-cells) and [Painted scenes](/docs/engine/painted-scenes).

```tsx
<AsciiShader
  field={FIELD}
  colorField={COLOR_FIELD}
  charset="dots"
  square
  cell={7}
  dither="bayer4"
/>
```

## Sub-cell charsets [#sub-cell-charsets]

Braille and quadrant glyphs aren't a ramp. Each one is a tiny bitmap: Braille has 8 dots in 2 columns of 4, quadrants have 4 squares in a 2×2 grid. The engine samples the field once per dot, lights each dot whose tone is above `threshold` (0.5 by default), and packs the lit dots into one glyph. Click the samples below:

```tsx title="examples/engine-subcells.tsx"
"use client";

// Braille and quadrant masks are bit fields.
// oxlint-disable no-bitwise

import { useState } from "react";

import {
  BRAILLE_DOT_BITS,
  brailleChar,
  QUADRANT_BITS,
  quadrantChar,
} from "@/lib/ascii";
import { cn } from "@/lib/utils";

interface CellEditorProps {
  title: string;
  bits: readonly (readonly number[])[];
  glyph: (mask: number) => string;
  base: number;
  initial: number;
}

// A grid of sub-samples you can switch on and off, and the one glyph they pack into.
const CellEditor = ({ title, bits, glyph, base, initial }: CellEditorProps) => {
  const [mask, setMask] = useState(initial);
  const char = glyph(mask);
  return (
    <div className="flex flex-col items-center gap-3">
      <div className="flex items-center gap-5">
        <div
          className="grid gap-1.5"
          style={{
            gridTemplateColumns: `repeat(${bits[0]?.length ?? 2}, 2.25rem)`,
          }}
        >
          {bits.flatMap((row, y) =>
            row.map((bit, x) => {
              const on = (mask & bit) !== 0;
              return (
                <button
                  key={`${x}:${y}`}
                  type="button"
                  aria-pressed={on}
                  aria-label={`Sample row ${y + 1}, column ${x + 1}`}
                  onClick={() => setMask(mask ^ bit)}
                  className={cn(
                    "border-border focus-visible:ring-ring size-9 rounded-sm border font-mono text-[10px] transition-colors outline-none focus-visible:ring-2",
                    on
                      ? "bg-ascii-ink text-background"
                      : "text-muted-foreground hover:bg-muted"
                  )}
                >
                  {bit.toString(16)}
                </button>
              );
            })
          )}
        </div>
        <span aria-hidden="true" className="text-muted-foreground font-mono">
          →
        </span>
        <span
          className="text-ascii-ink w-16 text-center font-mono text-6xl leading-none"
          aria-label={`Glyph ${char}`}
        >
          {char === " " ? " " : char}
        </span>
      </div>
      <p className="text-muted-foreground font-mono text-xs">
        {title} · mask 0x{mask.toString(16).padStart(2, "0")} · U+
        {(char.codePointAt(0) ?? base)
          .toString(16)
          .toUpperCase()
          .padStart(4, "0")}
      </p>
    </div>
  );
};

// Click samples to light them: Braille packs 2x4 into one glyph, quadrants 2x2.
export default function EngineSubcells() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-x-14 gap-y-8 p-6">
      <CellEditor
        title="braille"
        bits={BRAILLE_DOT_BITS}
        glyph={brailleChar}
        base={0x28_00}
        initial={0x01 | 0x10 | 0x04 | 0x80}
      />
      <CellEditor
        title="quadrants"
        bits={QUADRANT_BITS}
        glyph={quadrantChar}
        base={0x20}
        initial={1 | 8}
      />
    </div>
  );
}
```

Braille glyphs start at U+2800, and each dot adds one bit: dots 1 to 3 run down the left column (`0x01`, `0x02`, `0x04`), dots 4 to 6 down the right (`0x08`, `0x10`, `0x20`), and dots 7 and 8 sit on the bottom row (`0x40`, `0x80`). `brailleChar(mask)` builds the glyph and `BRAILLE_DOT_BITS[row][column]` gives each dot's bit. Quadrants use `quadrantChar(mask)` with top-left 1, top-right 2, bottom-left 4 and bottom-right 8.

Because the sample grid is finer than the cell grid, a field must not assume one sample per glyph. In GLSL, `res` is measured in sample rows; on the CPU, `createToneField(columns, rows, charset)` sizes the field for you.

Lower `threshold` lights more dots and thickens thin features. With `dither` set, each dot uses the matrix threshold at its sample instead, which turns soft tones into an even scatter of dots.

## Edge glyphs [#edge-glyphs]

The `edge` charset runs a 3×3 Sobel filter on the graded tones. Where the gradient is stronger than `edgeThreshold` (0.22 by default), the cell draws the glyph that runs across it: `|` for a left-right change, `-` for top-bottom, `/` and `\` for diagonals. Everywhere else it falls back to the `standard` ramp. `edgeChar(gx, gy)` does the direction step on its own, with y pointing down.

## Custom ramps [#custom-ramps]

Pass `chars` with your own glyphs, from empty to densest. It wins over `charset`. A ramp needs at least two glyphs; anything shorter falls back to `standard`.



```tsx
<AsciiImage src="/logo.png" chars=" .oO@" />
```

Start with a space so empty tones stay empty. Order the rest by how much ink they put on the cell, not alphabetically. The safest way to check is to look: a smooth gradient should brighten steadily with no glyph that jumps out.

## Calibration [#calibration]

A font draws `:` and `;` at almost the same weight, so an evenly spaced ramp wastes levels on glyphs you can't tell apart. The GPU renderer measures how much of the cell each glyph inks in your actual font (the glyph atlas does this once per font and size) and spaces the ramp by that coverage, blended with even spacing so every glyph stays reachable. `mapTones` on the CPU spaces glyphs evenly by default, which is what you want for exported text, because the reader's font is unknown. So at tone 0.5 the two can pick different glyphs. To match the GPU exactly in the browser, pass `positions: charsetRampPositions({ charset, metrics })` to `mapTones`.

## Helpers [#helpers]

| Helper                                                 | Returns                                                               |
| ------------------------------------------------------ | --------------------------------------------------------------------- |
| `resolveCharset(charset, chars?)`                      | The `Charset` to draw with, custom ramp first                         |
| `rampChar(charset, tone)`                              | The ramp glyph nearest a tone                                         |
| `eighthsBar(value, width)`                             | A row of eighth blocks, such as `"███▎  "` for 0.55 of 6              |
| `EIGHTHS_UP`, `EIGHTHS_RIGHT`                          | `" ▁▂▃▄▅▆▇█"` and `" ▏▎▍▌▋▊▉█"`, for bars and progress                |
| `BOX.light` / `heavy` / `double` / `rounded` / `ascii` | Corners, edges and crossings for frames                               |
| `SPINNERS`                                             | Frame sequences: `braille`, `orbit`, `arc`, `bar`, `quadrant`, `tick` |