There are two main approaches when it comes to fonts in Ebitengine games:
- Use vectorial fonts, either with
ebiten/v2/text/v2
oretxt
. This is the main way to do text rendering. - If you are making a pure pixel art game, you may consider using bitmaps (pixel art fonts rendered for one or more specific sizes). The rest of this document doesn't really deal with bitmap fonts, only vectorial fonts. If the bitmap fonts also have vectorial outlines, though, you can still use them with etxt.
Now I would start writing a FAQ for common problems with text rendering... but there are basically only two common issues:
- "I don't understand font.Face, DPI, origin position...: this part refers to the technical properties, concepts and implementation of font renderers. Some people don't care about this and can make fonts work anyway. That's ok. But if you care about it and want to know what you are doing, I have three pieces of advice:
- Read FreeType glyph conventions up to section IV or V. This is a must read for any developer trying to work with fonts on anything even half serious. It's excellently written, clear, concise and touches all the critical topics. You won't find a shorter and better reference.
- Ebitengine's
ebiten/v2/text/v2
package does not explain typography concepts. If you want to learn more about fonts and understand what you are doing, switch toetxt
. You can switch back later when you know more, butetxt
has great documentation that will help you learn a lot if you are getting started. - If you are still struggling with practical implementation, both Ebitengine and
etxt
have many examples. Look for them, read the code, execute them and learn. If there's something you don't understand, go back to FreeType glyph conventions or drop by Ebitengine's discord chat and ask for help.
- "My text looks bad, blurry, ugly, sad": ok, there are two parts to this...
- The first is that you need to understand with utmost clarity how what you draw on Ebitengine ends up being projected to a physical monitor. For this, Ebitengine's
Layout()
method is key. If you are confused about howLayout()
works, read this document that explains it in more detail. No point in trying to continue until you understand how that works. - The second is that even when you understand the previous point and are doing text scaling properly and using the full resolution of your window (and this is shown both on Ebitengine and
etxt
examples, so refer to those if you are still struggling), some rendering differences will still exist. They are detailed over the next section.
- The first is that you need to understand with utmost clarity how what you draw on Ebitengine ends up being projected to a physical monitor. For this, Ebitengine's
Both ebiten/text
and etxt
use Golang's x/image/font/sfnt
font library under the hood. This means we don't use the system's native stack or a top-tier font library. This means there are some limitations in text rendering that can make your text in Ebitengine games look different from other applications.
Ebitengine also added support for go-text/typesetting
with ebiten/v2/text/v2
, which helps overcome some of the issues the other two packages face.
Now, differences in text rendering between applications are a thing even outside Ebitengine. Browsers have been known to render text differently to each other for a long time. The goal of this section is not to help you make Ebitengine applications render text exactly like VSCode, Firefox, or any other specific application (as we all use different tech stacks under the hood), but rather to list limitations and enumerate some of the factors that may make results look different between renderers, as well as sharing some tips about what can be done regarding each one.
Let's start with the most painful limitations:
- Neither
ebiten/text
noretxt
have support for complex scripts (e.g. arabic, devanagari, etc). Hajime solved this withebiten/v2/text/v2
andGoTextFace
, so if you need to support those languages, consider going with that. I wrote more about the general problem of text shaping in this document. - Multiple limitations of the
x/image/font/sfnt
implementation. All these could be improved by moving golang/go#45325 forward:- No support for embedded bitmaps within sfnt fonts (SBIX table). While this would be nice to have, it's not a big deal for videogames either. If you need to stick to bitmaps in a pixel game and you don't like the results you are getting even after having read these tips, consider just going with bitmaps right away.
- No support for hinting. This is relevant for very small glyphs, but if you need that in a game you may be better using bitmap fonts directly. Having zoomable text in Ebitengine games would be rather unusual, so this isn't a prioritary issue. It's probably too much effort compared to what it adds in terms of rendering quality for most cases.
- No support for colored glyphs. This includes emojis and others. I personally do not care much about this.
- No support for variable fonts (weight and italics). I honestly don't care about this, it's a flashy feature but not very relevant in practice, at least for the kind of videogames we most often make with Ebitengine. Notice that
ebiten/v2/text/v2
does support this. Onetxt
's side, it's possible to use multiple fonts or faux rasterizers. - Technically, the lack of support for complex scripts is also due to
sfnt
limitations, and we could improve the situation without necessarily importing HarfBuzz as a whole if the relevant font tables were exposed.
Other differences and less significant limitations:
- We have no readily available subpixel-antialiasing. This can be implemented in
etxt
with a custom rasterizer + shader, but in games backgrounds can get messy with colors, so it's not even always the best choice. - Gamma correction. Neither
ebiten/text
noretxt
apply gamma correction when rendering glyphs. This means that when compared to other renderers, the glyphs may look thinner in Ebitengine. This is not a big deal in terms of implementation, but it would often be done with shaders, which can have a non-trivial impact on batching and performance, and requires extra information like the background color or the use of some heuristics. It's not that hard to implement if there was interest for it, though. x/image/font
andx/image/font/sfnt
have some subtle bugs and other oddities, like broken kerning scaling, incorrect application offont.Hinting
in some cases (which is not even actual font hinting but glyph quantization) and a few more things like that. Everything I could find was possible to fix directly onetxt
, but those issues remain inebiten/text
andebiten/v2/text/v2
(when usingStdFace
). Nothing is major enough to be obviously visible, though, so don't worry too much about it.- Outlining, underlining, strikethrough and other practical features are not readily available with high quality anywhere (low quality versions are quite easy to achieve). They could be implemented in
etxt
by anyone interested enough in them, but half of them have no "perfect solution". This is a topic of interest though, and there are some reference open source implementations (e.g. libASS outlining).