🌈 ESP32-S3 Rainbow: ZX Spectrum Emulator Board! Get it on Crowd Supply →
View All Posts
read
Want to keep up to date with the latest posts and videos? Subscribe to the newsletter
HELP SUPPORT MY WORK: If you're feeling flush then please stop by Patreon Or you can make a one off donation via ko-fi

Every time I put text on a TFT display with TFT_eSPI I end up staring at the same built-in fonts: chunky, jagged, and very 1980s. Which is a shame, because the library has been able to draw beautiful anti-aliased “smooth fonts” for years. The rendering was never the problem, the tooling was. The official way to make a smooth font involves installing the Processing IDE, editing a Java sketch, and running it again for every font at every size. The result is that most projects never bother.

So I built something to fix that.

fonts.atomic14.com is a free browser tool that turns any of 1,900+ Google Fonts (or your own TTF file) into fonts ready to use with TFT_eSPI, M5Stack/M5GFX or LVGL, with a pixel-exact preview of how they’ll look on your panel. Everything runs in your browser; no font ever gets uploaded anywhere.

Embedded Font Studio

The rest of this post is about what’s actually inside these font files, and the handful of things worth knowing before you use them.

What a “smooth font” actually is

TFT_eSPI’s smooth fonts are VLW files, a format inherited from the Processing project. A VLW is gloriously simple: a small header (glyph count, size, ascent, descent), a table of per-glyph metrics, and then raw 8-bit alpha bitmaps, one byte per pixel. No vector outlines, no hinting, no kerning. The font was rasterized on a desktop, and the microcontroller just blits pixels.

The “smooth” part is that alpha channel. Each pixel stores how much of the glyph covers it (0 to 255), and at draw time the library blends between your text colour and background colour in RGB565 space:

pixel = (fg × alpha + bg × (255 − alpha)) / 256

This is why tft.setTextColor(TFT_WHITE, TFT_BLACK) needs both colours for smooth fonts: there’s no transparency, only blending toward a known background. Draw over a photo and you’ll get fringes around the letters. That’s not a bug in your font, it’s how the format works.

PROGMEM or LittleFS?

You have two ways to get a VLW onto the chip, and the tool exports both:

  • A .h header with the font as a PROGMEM byte array. Easiest by far: #include it, call tft.loadFont(MyFont28);, done. The font lives in flash alongside your code.
  • A .vlw file on LittleFS or SD, loaded with tft.loadFont("MyFont28", LittleFS);. Better when you have several fonts or want to swap them without recompiling, at the cost of the filesystem-upload dance.

Either way, remember to add #define SMOOTH_FONT to your User_Setup.h. Forgetting it is the number one cause of “why doesn’t loadFont exist” compile errors.

Keeping the files small

A glyph costs 28 bytes of metadata plus width × height bytes of bitmap. At 28 px, a full Basic Latin set lands around 25-30 KB; a digits-only font for a sensor readout can be under 2 KB. Every character you leave out is flash you keep. The tool shows a live byte count as you change the character set, and you can click individual characters out of the grid. If all you need is °C and ten digits, ship °C and ten digits.

Quirks the preview will save you from

Two behaviours surprise almost everyone. I made sure the preview reproduces both, because it renders with a port of TFT_eSPI’s own drawing code rather than the browser’s font engine:

  1. Text wraps at the right edge. When a glyph won’t fit, smooth-font drawing jumps back to x=0 on the next line. It doesn’t clip.
  2. Spaces don’t use the font’s real space width. The library guesses (ascent + descent) × 2/7 pixels instead. Word spacing on the device will never quite match your desktop’s rendering of the same font, but the preview matches the device, which is the thing that counts.

And if a character shows up blank on the display, it’s almost always because it wasn’t in your subset. The degree sign is the classic victim: it’s not in Basic Latin, so a temperature readout quietly comes out as “23.5C”. Adding it to the extra characters box fixes it, and the preview will show you the gap before your display does.

Try it without any hardware

Every font you make can be tested on a simulated ESP32 and ILI9341 display in Wokwi: one click copies a complete test sketch with the font embedded and opens a pre-wired template project. Paste it in, press play, and you’re looking at your font rendered by the real TFT_eSPI library on an emulated panel.

The exported Orbitron font running on a simulated ESP32 and ILI9341 in Wokwi

The download also includes a ready-to-flash Arduino test project if you’d rather see it on real hardware.

LVGL too

The same subsetting and rasterization pipeline exports LVGL fonts: C arrays compatible with what lv_font_conv produces, at 1, 2, 4 or 8 bits per pixel. If your project uses LVGL or SquareLine Studio, grab that file instead.

Under the hood, for the curious

The VLW writer is byte-for-byte compatible with Processing’s PFont.save (verified against golden files), the renderer is checked against TFT_eSPI running in an emulator, and the LVGL writer is calibrated against real lv_font_conv output. The whole thing is client-side: opentype.js parses the font, a Web Worker rasterizes the glyphs to alpha bitmaps, and TypeScript writers produce the output bytes.

Go and make something less 1980s: fonts.atomic14.com. And if you’re still choosing a display to pair it with, the ESP32 display boards database has every popular board in one filterable table. I’d love to hear how you get on.

Related Posts

ESP32-C3 0.42 OLED - Picked up a stack of ESP32-C3 + 0.42" SSD1306 modules and followed an existing guide, but I wasn’t keen on the 128x64-with-offset bodge. I dug into U8g2 and created a proper 72x40 SSD1306 constructor so drawing uses native coordinates. Cleaner code, same tiny display, job done.
Web Serial Plotter - I've created a web-based serial plotter for easy data visualization from Arduino and ESP32 projects. It boasts real-time plotting, interactive controls, data analysis, and a serial console—all within your browser with no extra installations needed. Give it a try and streamline your microcontroller data analysis!
ESP32-S3 Hardware SPI on the Adafruit ST7789 - I've had some commenters point out the issue with the slow display updates in my recent Arduino Nano ESP32 video. It turns out, the software SPI of the Adafruit_ST7789 library was the culprit. Lo and behold, the solution is simple - using the hardware SPI constructor of the library. Apparently, this isn't well documented, so I wrote some code to serve as reference for myself and others who might run into the same snags. Trust me, the difference in speed is absolutely bonkers. Check out the video to see the magic in action.
Esp32 s3 zx spectrum - In a bid to quench my nostalgia and flex my ESP32 chops, I managed to get a ZX Spectrum emulator running on my ESP32-TV board! Then, spurred on by PCBWay's new full color silk screen service, I pursuit the audacious task of recreating the ZX Spectrum's iconic keyboard. It's been quite the joyride - wrangling touch pins, shrinking screens and creating a thing of beauty on PCB. It's not quite ready for the spotlight, but keep an eye on my newsletter for more eagerly-awaited updates. It's like the Spectrum is reborn!
ESP-IDF v6 in VS Code: Blink (and debug) on the ESP32-S3 - I finally crawled out of the v5.x cave, installed ESP-IDF v6.0.1 with the VS Code extension, spun up the Blink example on an ESP32-S3, fixed a finicky flash by switching to UART, then used menuconfig to set the LED pin, bump the CPU to 240 MHz, use the full 16 MB flash, and enable 8 MB PSRAM. Wrapped it up with proper USB JTAG debugging in VS Code. If you’ve been putting off the IDF, v6 in VS Code is a super-smooth on-ramp.

Related Videos

ESP32 Audio Monitor using TFT_eSPI (TTGO) -
DIY eBook Reader - Build your own Kindle using an ESP32 - Build your own DIY e-Reader with an ESP32, an e-paper display, and some simple coding. This project decodes ePUB files, parses HTML files, and renders texts and images on an e-paper display, while optimizing battery usage.
Streaming Video and Audio over WiFi with the ESP32 - In this video, we dive into a hardware hack combining several components to create my version of the TinyTV, complete with a remote control, and video streaming over Wi-Fi. We challenge the speed of image display, using different libraries and tweaking performance for optimal results. We explore Motion JPEG or MJPEG to decode and draw images quickly, and even reach about 28 frames per second. We also catered audio using 8-bit PCM data at 16kHz, and deal with syncing both video and audio streams. Finally, we add some interactive elements allowing us to change channels and control volumes, with a classic static animation thrown in for good measure. There's a few hiccups along the way, but that's part of the fun, right?
Test Arduino & ESP32 Codes in Your Browser: WokWi Simulator Unveiled! - Discover WokWi, an online Arduino and ESP32 simulator with incredible functionality! Test your firmware, run Python code, and simulate various components in this powerful web-based tool.
DIY e-Reader update - ported to the M5Paper - Discover the latest updates on the DIY e-reader project, featuring the sleek M5 paper device with an SD card for storing numerous books, a jog button for navigation, and a crisp display! Check out the Github link and try it out.
HELP SUPPORT MY WORK: If you're feeling flush then please stop by Patreon Or you can make a one off donation via ko-fi
Want to keep up to date with the latest posts and videos? Subscribe to the newsletter
Blog Logo

Chris Greening


Published

> Image

atomic14

A collection of slightly mad projects, instructive/educational videos, and generally interesting stuff. Building projects around the Arduino and ESP32 platforms - we'll be exploring AI, Computer Vision, Audio, 3D Printing - it may get a bit eclectic...

View All Posts