Draw Any Letter with LEDs
Overview
In this tutorial you will build a reusable <LedLetter> component that draws
any capital letter (A–Z) by placing 0402 LEDs along the letter's outline path.
The layout is fully automatic — you supply a letter and a size, and math does
the rest.
Step 1: Sample points along an SVG path
The @tscircuit/alphabet package exports svgAlphabet, a map from each
character to a normalized SVG path string (coordinates in [0, 1]). We need
to convert that path into a list of evenly-spaced {x, y} points so we know
where to place each LED.
Step 2: Add current-limiting resistors
Each LED string needs a resistor (typically 68–100 Ω for a red 0402 LED at 5 V). We add one resistor per LED wired in series.
Step 3: Build the reusable LedLetter component
Now we wrap everything in a component that accepts any capital letter and a
scale factor. All 26 letters (A–Z) are supported using the path data from
@tscircuit/alphabet.
Step 4: Complete usage example
Here is the complete <LedLetter> component with all 26 letters supported,
ready to copy into your own project.
import { svgAlphabet } from "@tscircuit/alphabet"
function samplePath(pathStr: string, n: number) {
const points: { x: number; y: number }[] = []
const tokens = pathStr.trim().split(/\s+/)
let i = 0
while (i < tokens.length) {
const cmd = tokens[i++]
if (cmd === "M" || cmd === "L") {
points.push({ x: parseFloat(tokens[i]), y: parseFloat(tokens[i + 1]) })
i += 2
}
}
if (points.length < 2) return points
const arcLen = [0]
for (let j = 1; j < points.length; j++) {
const dx = points[j].x - points[j - 1].x
const dy = points[j].y - points[j - 1].y
arcLen.push(arcLen[j - 1] + Math.sqrt(dx * dx + dy * dy))
}
const total = arcLen.at(-1)!
const result: { x: number; y: number }[] = []
for (let k = 0; k < n; k++) {
const target = (k / (n - 1)) * total
let seg = 0
while (seg < arcLen.length - 1 && arcLen[seg + 1] < target) seg++
const segLen = arcLen[seg + 1] - arcLen[seg]
const t = segLen === 0 ? 0 : (target - arcLen[seg]) / segLen
result.push({
x: points[seg].x + t * (points[seg + 1].x - points[seg].x),
y: points[seg].y + t * (points[seg + 1].y - points[seg].y),
})
}
return result
}
interface LedLetterProps {
letter: string // Any capital A–Z letter
power: string // Net name for VCC (e.g. "net.PWR")
gnd: string // Net name for GND (e.g. "net.GND")
offsetX?: number // PCB X offset in mm
offsetY?: number // PCB Y offset in mm
width?: number // Letter width in mm (default 18)
height?: number // Letter height in mm (default 24)
nLeds?: number // Number of LEDs (default 14)
namePrefix?: string // Prefix to avoid name collisions
}
export const LedLetter = ({
letter,
power,
gnd,
offsetX = 0,
offsetY = 0,
width = 18,
height = 24,
nLeds = 14,
namePrefix = "",
}: LedLetterProps) => {
const path = svgAlphabet[letter.toUpperCase() as keyof typeof svgAlphabet]
if (!path) return null
const pts = samplePath(path, nLeds)
return (
<>
{pts.map((p, i) => {
const px = offsetX + (p.x - 0.3) * width
const py = offsetY + (0.6 - p.y) * height
const ledName = `${namePrefix}${letter}_LED${i + 1}`
const resName = `${namePrefix}${letter}_R${i + 1}`
const midNet = `net.${ledName}_A`
return (
<>
<resistor
key={`r${i}`}
name={resName}
resistance="68ohm"
footprint="0402"
pcbX={px - 1}
pcbY={py}
connections={{ pin1: power, pin2: midNet }}
/>
<led
key={`l${i}`}
name={ledName}
color="red"
footprint="0402"
pcbX={px}
pcbY={py}
connections={{ anode: midNet, cathode: gnd }}
/>
</>
)
})}
</>
)
}
Usage:
export default () => (
<board width="60mm" height="35mm">
<LedLetter letter="A" power="net.PWR" gnd="net.GND" offsetX={-20} namePrefix="A_" />
<LedLetter letter="B" power="net.PWR" gnd="net.GND" offsetX={5} namePrefix="B_" />
</board>
)