Tool to "thin" out a font to only include used characters?

I’ve been searching to see if a tool like this exists, so figured I’d just ask.

My game has a finite set of strings, so I don’t need every character in a font. I estimate only shipping the actual characters my game needs can save about 100 bytes of flash.

If no tool for this exists, I plan to make one. You give it as input the strings that are in your game, like “new game”, “game over”, etc for example, and the output is a progmem array of the characters you need, the strings encoded such that they can be directly printed from the characters, and a function to do the printing (probably relying on a drawBitmap function to do the real drawing) So basically strings but instead of ascii encoding, a custom encoding based on what characters you actually use. Then if you ever change your strings, just run the tool again.

1 Like

There is no tool like that yet! So go for it.

2 Likes

Cool idea!! Create a new font table with just the characters you need up front, then only program in those graphics.

It’ll probably have pretty limited uses, you really do need totally static strings in your game. But I also think it could be a decent sized win for games that can use it.

I’ll get started on it tonight. I’m going to hardcode tinyfont in for the characters and see how it goes as a first pass.

Longer term I could see the tool accepting other fonts, and also the tool could tell you if just using sprites instead of a font is better for you (say you rarely/never repeat letters)

It may be possible to include a macro to turn on and off printing numbers using this, too. That would be cool.

That’s doable, but I think it might take some exploration. Printing ints as strings requires some kind of int to char conversion, which would eat into the savings a bit.

Games that can use this tool would also be pretty easily localized into other languages.

I’m not sure what you mean by this.

I am guessing that if your game has a number of strings, the likelihood of you stripping out a lot of letters is minimal - you may lose v k x j q z if you are luck. If you save 4 bytes per letter that is only 20 to thirty bytes in the character images.

I wonder if better savings might be made ‘packing’ the characters into a binary string. If you had a full alphabet and some punctuation, you could use 5 bites per character.

I am guessing that if your game has a number of strings, the likelihood of you stripping out a lot of letters is minimal

For my game, I use 20 capital letters, space and colon. TinyFont includes 96 characters, so I am chopping out 74 characters. 2 bytes per character (two 4x4 characters are packed into one 4x8 tile). So I am saving about 148 bytes there. Then there is the cost of rendering the strings, but TinyFont and Print have that cost too. I predict my tool’s string rendering will be smaller than TinyFont’s, but will have to see. Storing my strings either in ASCII or in the custom encoding has the same cost.

I think it really depends on the game. Games that are light in text but still have a little (like the menu on the title screen), may see a nice savings compared to including an entire font.

I should have a working version of this done tonight, so I’ll post what my savings was.

I wonder if better savings might be made ‘packing’ the characters into a binary string. If you had a full alphabet and some punctuation, you could use 5 bites per character.

I think that’s basically the opposite problem, for games that have lots of text. In their case, packing into 5 bits might be a good win.

1 Like

I am not overly familiar with TinyFonts but assume in the 96 characters there is both upper and lower case? I assumed upper only.

I extended the Print functionality in my 3x5 and 4x6 fonts but I don’t think it was necessary. I am keen to see how much memory you can save.

Here is TinyFont (taken from the repo)

tinyfont-preview

So yeah, it’s upper, lower and some punctuation. The large characters at the bottom are standard print, added for comparison.

extended the Print functionality in my 3x5 and 4x6 fonts

Which characters did you include in your fonts? it’s possible compared to your fonts this isn’t much of a win. But even a few bytes I’ll take :slight_smile: I’ve freed up 2k for my game, and I estimate I need about 2.5k to finish it successfully.

1 Like

That doesn’t sound right.

If you’re only using 22 characters you should be able to pack those into 5 bits per character, or 3 characters every 2 bytes.
(Which I believe is what @filmote was suggesting.)

A 9 byte ascii string should be reduced to 6 bytes.
(Although granted, it them becomes a bit of an effort to put text into the game.)


My offer still stands :P


By the way, instead of copy-pasting something someone has said and then wrapping it in a code block, you should be able to just highlight what they’ve said and press the ‘quote’ button that appears, which gives you a full quote with context and notifies them that you’ve quoted them.

In the comment editor it ends up looking like:

[quote="city41, post:11, topic:6365"]
I’ve freed up 2k for my game, and I estimate I need about 2.5k to finish it successfully.
[/quote]
1 Like

MidnightWild3
In Midnight Wild, I print some int’s in a smaller font than just using Arduboy2.print() . (Bottom-left corner.)

Ah yes, that does make sense too, I see what filmote was getting at. But I suspect extracting 5 bits out would take a lot of code, as characters would span across byte boundaries. In my case, the code cost might kill any savings. A game that has a ton of text though would probably benefit.

it’s still worth trying, so I’ll give it a shot, probably later this week.

ah nice, thanks! I’ve never used this forum software before.

I will take you up on it, I would definitely appreciate the review. I just want to clean up some bad choices I made first, some parts of the codebase are really messy.

Only if you allowed them to.
I think it would probably be easier (and possibly more efficient) to simply pack 3 characters into 2 bytes and then ignore the wasted bit.

So you end up with something like:

uint16_t chars = pgm_read_word(&string[index]);

uint8_t first = static_cast<uint8_t>(chars & 0x1F);

chars >>= 5;
uint8_t second = static_cast<uint8_t>(chars & 0x1F);

chars >>= 5;
uint8_t third = static_cast<uint8_t>(chars & 0x1F);

Of course, bitshifting still isn’t the best on an AVR processor (no barrel shifter), but hopefully it’s good enough.

Alternatively you could treat the high bit as a marker indicating whether the byte is 1 char or 3 chars (so it’s easier to pack chars that aren’t multiples of 3) and then make use of chars < 0 to determine whether the high bit is set, which should be relatively small and fast still.

You could even pack that into a loop, which would probably be slower but should be smaller.

Discourse. It’s relatively modern. One of the founders of it also founded Stack Overflow and is relatively well known (as ‘coding horror’).

Fair enough.

This netted me 936 bytes saved. However, it has much more to do with removing TinyFont than the “font thinning”. TinyFont adds 928 bytes to a trivial Hello World Sketch. I’m a little perplexed how I saved 8 more bytes on top of that :man_shrugging:

By using my little font thinning approach, I am instead using the same drawBitmap function that I use to draws all the other sprites in my game, so that function is already a sunk cost.

This worked out well, although in hind sight I should have looked to see what TinyFont’s cost was first. Looking at other font solutions, they seem to take a similar approach as TinyFont.

It might turn out for my game, turning the strings into regular bitmaps might be better.

But for now going to enjoy this 900 byte windfall! I believe I can finish the dang game now! :slight_smile: (I’m still interested in saving even more bytes, so I can put back in some features I cut…)

I could use some of that magic!

Ah ha! Forgot to add Tinyfont::print(int) into the test Sketch. With both print int and print string, Tinyfont takes up 1104 bytes. Ok, that makes a lot more sense.

(Since my print(int) uses were printing a number between 0 and 6, I replaced those with drawing up to six boxes, as I lost the ability to print ints. I think it looks better this way.)

Hey, cool idea. If I can recommend, use python. Here’s a snippet I made in like a minute that creates you the set of characters:

from sets import Set

strings = [
  "hello",
  "world",
  "Arduboy",
  "123"
  ]

chars = Set()

for s in strings:
  for c in s:
    chars.add(c)

print(sorted(chars))

Nothing beats python for these kinds of things :slight_smile:

It might be best to make this some step in preprocessing if possible using directives and macros.