Draw monospace text on Android screens via ADB — because some apps only give you cursive fonts.
| JetBrains Mono | IBM Plex Mono Thin |
|---|---|
![]() |
![]() |
Lunar.Mono.mp4
Lunar.IBM.mp4
I wanted to put Console.Beep(); on my Lunar metal bank card. You know, as a dev joke.
One problem: Lunar's card designer only offers two fonts — both cursive handwriting styles. Code humor doesn't land when it looks like a wedding invitation.
But wait — there's a "free draw" option! You can hand-write anything you want. Surely I can just... draw monospace letters with my finger?
Spoiler: I cannot. My handwriting is terrible.
So naturally, the solution was to:
- Extract glyph vector paths from fonts using SkiaSharp
- Convert Bezier curves to line segments
- Inject MotionEvents via ADB to simulate finger drawing
- Fine-tune with optional offset passes and priming wiggles
It worked.
Somewhere in Denmark, a Lunar employee is staring at a moderation queue wondering how someone submitted perfect monospace code in a free-draw field that only supports finger painting.
AndroidDraw takes text and a font, extracts the vector paths, and "draws" them on your Android device's screen using ADB input injection. It's essentially a robot finger that can write in any font.
git clone https://github.com/Memphizzz/AndroidDraw.git
cd AndroidDraw
dotnet build -c Releasedotnet run -c Release -- "Your text" --x <X> --y <Y> --width <W> --height <H>| Option | Description | Required |
|---|---|---|
--x |
Target area X position (pixels from left) | Yes |
--y |
Target area Y position (pixels from top) | Yes |
--width |
Target area width to fit text into | Yes |
--height |
Target area height | Yes |
--font |
Font family: JetBrains Mono (default), IBM, Hershey |
No |
--thin |
Skip offset pass for thinner lines | No |
--wiggle |
Characters that need priming wiggles (e.g. ";") |
No |
--simple |
Use built-in geometric/LCD style font | No |
--delay |
Delay between strokes in ms (default: 50) |
No |
--device |
ADB device serial (for multiple devices) | No |
--dry-run |
Print commands without executing | No |
Three fonts are embedded:
- JetBrains Mono (default) — the classic programming font
- IBM Plex Mono Thin (
--font "IBM") — thinner strokes, good for line thickness requirements - Hershey Simplex (
--font "Hershey") — classic single-stroke engraving font
These coordinates worked for pixel-perfect centering on the Lunar card designer (OnePlus Open, folded):
# Using IBM Plex Mono Thin for thinner lines (Lunar requires pen-weight strokes)
dotnet run -c Release -- "Console.Beep();" --font "IBM" --wiggle ";" --x 250 --y 1200 --width 620 --height 300Finding your coordinates: Use --dry-run to preview, then test in a drawing app like Google Keep to dial in the position before attempting on the target app.
Uses SkiaSharp to extract vector paths from font glyphs. The paths contain MoveTo, LineTo, QuadTo, and CubicTo segments that define each character's shape.
Quadratic and cubic Bezier curves are converted to line segments (30 segments per curve) for smooth drawing.
Ramer-Douglas-Peucker algorithm reduces point count while preserving shape, making drawing faster without losing quality.
Sends touch events to simulate finger drawing:
adb shell input motionevent DOWN x y
adb shell input motionevent MOVE x y
# ... more MOVE events along the path
adb shell input motionevent UP x ySingle-pixel strokes can be too thin for some drawing apps. By default, AndroidDraw runs two passes:
- Main pass — draws the stroke at original coordinates
- Offset pass — redraws at +1px diagonal (X+1, Y+1) to fill gaps
Use --thin to skip the offset pass for thinner lines.
- Wake-up tap — primes the touch system before drawing
- Priming wiggles — optional per-character wiggles to ensure stroke start registers (use
--wiggle "chars") - Spiral dots — periods and small punctuation are drawn as filled circles (not taps)
- Progress output — shows batch progress during drawing
- Test coordinates first — Use a drawing app like Google Keep to dial in your
--x,--y,--width,--heightbefore attempting on the target app - Line too thick? — Try
--font "IBM"with--thinfor the thinnest possible strokes - Stroke start missing? — Add that character to
--wiggle(e.g.,--wiggle ";")
Just one:
<PackageReference Include="SkiaSharp" Version="3.119.1" />Three fonts are embedded as resources — no external font installation required.
A metal bank card with Console.Beep(); in monospace, achieved through:
- Font vector extraction
- Bezier curve interpolation
- ADB touch injection
- Sheer determination to make a dumb joke work
Worth it.
MIT
- Claude (Anthropic) for pair-programming this entire thing
- SkiaSharp for font path extraction
- JetBrains Mono for being the best programming font
- IBM Plex for the thin variant that passed Lunar's guidelines
- Lunar Bank for having a free-draw option (and only cursive fonts)
- The Lunar moderation team employee who approved this and is probably still confused

