return to table of content

Writing a TrueType font renderer

vitiral
18 replies
5d20h

I wonder, is there something on the complexity curve between 8x8 bitmap and fullblown ttf with all the complexity as you've outlined?

It would be nice if there were a solution which gets 90% of the value in 10% of the code.

mhd
3 replies
5d16h

Hershey fonts[0] look a bit odd on high-res pixel screens, but one might be able to cope with that by carefully selecting sizes and fudging with the definitions. Definitely low-code.

And once upon a time, X11 had another weird little format besides PS and TTF fonts, namely "Speedo" fonts[1]. Also used in plenty of other software, as PS/TTF weren't that common and it was made by Bitstream, one of the major font foundry (because no font format helps if there are no legal fonts available for it).

Alas, both not really in use these days.

[0]: https://en.wikipedia.org/wiki/Hershey_fonts

[1]: https://en.wikipedia.org/wiki/Bitstream_Speedo_Fonts

vitiral
0 replies
4d3h

I haven't looked deeply yet, thanks for the resources!

vitiral
0 replies
4d3h

I found this reference for Hershey Vector font

https://paulbourke.net/dataformats/hershey/

It seems to describe a "pen" which draws the character. It is indeed pretty cool!

082349872349872
0 replies
5d10h

Note that Hershey's (physical!) pens were about 3 times as large as his coordinate system; rendering them with a suitable thickness gives me reasonable results even at higher resolutions.

(like with the pixel grill on CRTs, often older digital formats were subject to some sort of analogue low-pass filtering before being perceived)

kragen
2 replies
5d17h

tex's metafont format might be simpler or more complex than truetype, depending on your utility function. sdf fonts are clearly in the range you want, as several commenters have said, and likewise for proportional and scalable bitmap (and grayscale, or color) fonts. also worth mentioning are stroke fonts, especially hershey fonts, and polygonal outline fonts without beziers

finally, an approach that's conceptually simpler that i've never seen used (though metafont comes close) is to represent the font as a subroutine in a turing-complete bytecode that takes a glyph index as input and produces an image with font metrics data as output. something like the universal machine from the cult of the bound variable is only about a page of code. this requires you to put the bezier rasterization and polygon filling code in your font file instead, so it doesn't simplify the total system

vitiral
1 replies
3d17h

Thanks! This looks like a really good summary of options.

kragen
0 replies
3d16h

i'm delighted it's useful!

lifthrasiir
1 replies
5d13h

I once worked on an almost-bitmap font with its own description language [1] that can be extended to work on subpixel grids. I went as far as combining marks (not in the public repository though).

[1] https://github.com/lifthrasiir/unison

vitiral
0 replies
3d16h

Unison looks more like what I was thinking, thanks!

grishka
1 replies
5d19h

Proportional bitmap fonts in several fixed sizes. One place where they were used a lot that comes to mind is those phones from the 00s, but also many other embedded devices that have a screen with text-based menus.

Someone
0 replies
5d8h

That’s what the Mac used, too. For examples, see http://kare.com/apple-icons/

Before TrueType (System 7, 1991), it was the only thing the OS supported out of the box.

(Adobe Type Manager (https://en.wikipedia.org/wiki/Adobe_Type_Manager) is a bit older than 1991, but (obviously from its name) wasn’t an Apple product)

colonwqbang
1 replies
5d20h

Using a better, higher-resolution bitmap font? They don't have to look as awful as that

https://terminus-font.sourceforge.net/img1/12x24b.gif

vitiral
0 replies
5d18h

That does look a lot better!

vidarh
0 replies
3d22h

libschrift is ~1500 lines of C: https://github.com/tomolt/libschrift

My Ruby rewrite is ~600 lines: https://github.com/vidarh/skrift

Libschrift is very readable.

I did my Ruby rewrite basically just top to bottom before reorganizing it. Mine is... readable if you're well versed in Ruby, but still has some warts where it's less than idiomatic Ruby because I stuck closely to the original.

Basically TTF has a crufty binary format, but the basic font data if you're willing to ignore ligatures, hinting, OpenType support and emoticons, is fairly simple (it's basically a bunch of polygons consisting of quadratic beziers and lines, and quadratic beziers are easy to tesselate into lines if you don't want to do a more complex curve renderer), just error-prone to figure out.

If you want/need OpenType you need to support cubic beziers on top of that, which isn't that bad. If you want to support emoticons you need to support a subset of SVG (!)...

So TTF without those bits is pretty much the halfway point.

Also do look at the Canvas C++ header implementation linked in this comment[1]. It's readable, and more featureful than libschrift or my Ruby rewrite, and it's still small while packing a full rendering library in there not just the font renderer. I intend to pillage it (with credits) for ideas ;)

[1] https://news.ycombinator.com/item?id=38839114

stravant
0 replies
5d20h

You're looking for SDF fonts.

Very simple at their core but you can incrementally do more work to increase the fidelity you get out of them.

rzzzt
0 replies
5d17h

I nominate the BGI vector fonts that you could use in Turbo Pascal programs.

pcwalton
0 replies
5d13h

Just use a TrueType subset with no hinting. stb_truetype implements one in 5k lines of code and that includes CFF format outlines. You could omit those and get it even smaller.

djmips
0 replies
5d18h

Here's a link to a Valve paper from 2007 Siggraph but certainly there's been a lot of dev since then but this is kind of cool still!

https://github.com/Michaelangel007/game_dev_pdfs/blob/master...

userbinator
10 replies
5d19h

I think TTF isn't that difficult if you just want the basics; I wrote a TTF viewer that simply dumped the outlines into GDI and let the latter do the rasterisation, without any hinting or other fancy stuff. Less than 400 LoC in total including the UI code, and the binary was 7KB.

vidarh
6 replies
5d19h

400 LoC is impressive - my Ruby translation of libschrift is about 680 lines now, and of that 345 lines is parsing the damn TTF files. I might have to revisit it ;)

userbinator
5 replies
5d18h

It's ~240 LoC of C to take a glyph from the file and paint it using GDI, including lots of debugging prints. Of course, error checking is minimal, but I think getting to the outline data itself isn't that difficult, since all you need to get to is a list of points and contours and whether they're on-curve or not.

vidarh
4 replies
5d18h

Is it open source? Would love to see. Doesn't matter if it's lacking error checks - still impressively small, and doubly so for C.

a_e_k
2 replies
5d10h

I have a small TTF implementation in C++ that's only a little bigger than that and is open source. It's part of my canvas_ity single-header library [0] that's around 2300 LOC / 36 KB object size and implements a C++ version of most of the 2D HTML5 canvas spec [1].

The core implementation of the TTF parsing and drawing is in L1526-L1846 with another small bit at L3205-L3274 of src/canvas_ity.hpp.

It's something of a toy implementation that only supports western left-to-right text, and doesn't do any hinting at all, nor kerning, nor shaping. But it's enough to draw a basic "Hello world!" using any typical TTF file.

The test suite in test/test.cpp L84-304 embeds a few custom Base64-encoded TTF files. They're small and only have a few glyphs but they do exercise a number of interesting edge cases in the OpenType TTF spec [2]. Have a look at the HTML5 port of the test suite at test/test.html in different browsers to see how their canvas implementations render those fonts.

[0] https://github.com/a-e-k/canvas_ity

[1] https://www.w3.org/TR/2015/REC-2dcontext-20151119/

[2] https://standards.iso.org/ittf/PubliclyAvailableStandards/c0...

vidarh
0 replies
5d10h

That's great. Thanks. For my use the lack of kerning doesn't matter much as I mostly use it for my terminal, and so monospaced, and I haven't added hinting either.

vidarh
0 replies
5d6h

Having read through this, I love it. Very readable for C++...

In terms of the actual TTF bit, if you pull out everything needed it's probably somewhat bigger than what I already have, but that's because libschrift and my Ruby translation does tesselation and rasterisation that's doing the very barest minimum needed for the basic TTF rendering and yours is far more generic (and let's face it: fancier/more featureful in a good way) rendering of the polylines.

I've already half-wanted to pull that out of my Ruby port anyway and doing a basic canvas, so I might just look to yours for inspiration on doing that in a way that can be reused if I get the urge to extend it further later.

userbinator
0 replies
5d16h

Sorry, no. I used part of the code in a commercial product.

Sharlin
1 replies
5d19h

Well, yes, but GDI isn't exactly available when you're writing your own OS like the author of the article.

vidarh
0 replies
5d19h

The rasterisation doesn't take much anyway. My libschrift Ruby translation does the rendering in around 100 or so lines. The original libschrift is in C and only takes about twice as much. Parsing the TTF files takes more code.

txdv
0 replies
5d19h

link please

darknavi
10 replies
5d18h

Working in games, font code is truely the depths of hell that you try to punt to someone else as often as you can.

Maybe one day we will support right to left text!

rijoja
3 replies
5d17h

The other day I saw a comment saying that fonts are made complicated by turning them into programs, which you can legally protect as IP, is there any truth to that?

xyzzy_plugh
0 replies
5d14h

Roughly true. Rasterized fonts are typically not copyrightable. The font files, or as you suggest, programs, are.

If you see a font you think is cool, you can pretty much rip off the style without any real consequences. This is often why font licenses for print media are priced very differently than digital media where the font is redistributed.

omnimus
0 replies
5d7h

To clarify fonts are not made complicated so they can legally pose as software. There are many real reasons why they are complicated. Yes they are licensed as software (they are sort of plugins) but its not related to the complicateness.

drzaiusx11
0 replies
5d15h

PostScript fonts exist and PostScript is a Turing-complete language. Source code is copyrightable, so this tracks

dclowd9901
3 replies
5d14h

Maybe you know this one: for UI in games, do devs create an entire windowing environment from scratch? It seems like that would be the best way to do it but man what a lot of work.

mewse
1 replies
5d8h

Sometimes. It depends on the UI requirement for the game.

I have seen some extremely-UI-heavy games which do exactly what you say; building a whole windowing environment entirely from scratch. (I'm working on a game that's doing that right now!).

But most major game engines provide a basic UI toolkit if your needs aren't too intense, and those are often more than enough for most games and are a whole heap less effort; they can typically be themed so that your UI doesn't look like everybody else's UI, and if all you need is a single window with some widgets and buttons and maybe a text field, that's probably going to be more than enough for you. Like, 95% of games would probably be totally fine with just that.

Maybe five years back I worked on and adjacent to a couple large games that were building their UI in an embedded copy of Flash (which wasn't as dead as people think!) Not sure whether large games are still doing that now, but it wouldn't surprise me if there are still some holdouts building their game UIs entirely in Flash!

speps
0 replies
5d5h

Ubisoft used to use ScaleForm, the alternative Flash runtime for games, for a lot of the Assassin's Creed games.

I know some games use a specialised version of WebKit, for example Sea of Thieves uses Coherent UI[1]. There's also Ultralight[2] (ex Awesomium) which is similar.

Games using vanilla Unreal Engine can use what they call UMG, but otherwise there's plugins for most frameworks.

Some other games have completely custom frameworks of course.

[1] https://coherent-labs.com/Documentation/cpp/ [2] https://ultralig.ht/

cardiffspaceman
0 replies
5d13h

I was part of a team that delivered four SKUs, DOS and Windows versions of two games. We made a fairly complete version of a GUI environment so that the DOS games would look like the Windows versions. We didn’t rasterize the TrueType fonts. We only used one size in Windows, so we used a bitmap font for DOS and it was enough.

mdaniel
0 replies
5d15h

relevant: https://faultlore.com/blah/text-hates-you/ ("Text Rendering Hates You"; discussed quite a bit but under its old URL https://hn.algolia.com/?query=text%20rendering%20hates%20you... )

It's not quite one of the "misconceptions programmers have about ..." but in the same vein, IMHO

manchmalscott
0 replies
5d1h

Godot first merged support for RTL/BiDi about three years ago. It looks like unreal might also support those too but I can’t confirm that.

ianlevesque
7 replies
5d20h

Inexplicably, though, the TTF stores all fields in big endian

Both the 68k and PPC (as configured for macs) were big endian.

mannyv
2 replies
5d

Little endian is the vast majority of stuff today due to the dominance of x86, but back when big-endian ruled the day. That's why network byte order is big-endian.

peterfirefly
1 replies
3d23h

VAX and PDP-11 were also little-endian.

mark-r
0 replies
3d17h

Were those the first byte-addressable architectures? If they were you think they'd have had a bigger influence.

jpgvm
2 replies
5d15h

Along with MIPS. The significance of which is that it led to a lot of network gear using one of these 3 architectures. Now most network data planes are in ASIC and increasingly these BE ISAs are being supplanted by ARM but for a long time it held out as a bastion for PPC in particular.

swiftcoder
1 replies
5d8h

ARM still has big-endian mode for this niche. Modern ARM cores even have dynamically switchable endian for data

codyd51
0 replies
5d6h

That's awesome. Thanks for sharing.

codyd51
0 replies
5d9h

Of course, this makes sense - thank you!

hgs3
7 replies
5d19h

Correct me if I'm wrong, but I think font hinting isn't needed for high DPI displays? I believe macOS Mojave removed subpixel anti-aliasing because Apple doesn't ship anything but high DPI displays anymore.

vidarh
3 replies
5d19h

"Needed" is subjective. Libschrift[1] is an example of a TTF renderer that doesn't do any. I translated Libscrift to Ruby[2], and I considered whether to add support for it, and decided it wasn't worth it for my use (it's now the font-renderer for my terminal, and by extension my editor) despite only using it on 1920x1080 on a 27" monitor.

My reasoning for not bothering was much what you suggest - if it's acceptable to me now on a resolution that low, I'm not sure I see the point in supporting even worse conditions given how cheap 4K displays are. The need for hinting is only going to go down.

Maybe one day, but I'll note e.g. FreeType also did a lot of work on auto-hinting because as it turns out the hinting in a lot of TTF files is pure garbage.

[1] https://github.com/tomolt/libschrift

[2] https://github.com/vidarh/skrift - X11 integration in https://github.com/vidarh/skrift-x11

jahewson
2 replies
5d18h

FreeType did auto-hinting because Microsoft held patents on some of the hinting engine instructions. These finally expired in 2010.

vidarh
1 replies
5d18h

That might well be the original motivation, but note e.g. ttfautohint development 2011 or so onwards [1] intended to strip hinting programs and replace them with the output of the autohinter because so many fonts have awful hints.

[1] Not sure exactly when the ttfautohint work started, but both Microsoft and Google donated money to it in 2011.

jahewson
0 replies
5d18h

It’s true that autohinting is nowadays the norm but I’d be more generous to those old fonts as they were aggressively hinted for black-and-white rendering. It’s not until ClearType was enabled by default in Windows Vista c. 2007 that those old hints became undesirable.

grishka
1 replies
5d19h

because Apple doesn't ship anything but high DPI displays anymore

People still connect their Apple computers to low-DPI monitors all the time. High-DPI monitors are still a rarity. There's the LG UltraFine, the Studio Display (too expensive for what it is), the Pro Display XDR (the $1000 stand lmao), and that's really it.

I'm writing this comment on a 2K monitor connected to a modern MacBook.

Liskni_si
0 replies
3d21h

People still connect their Apple computers to low-DPI monitors all the time.

They do, but the fonts are visibly blurry on those, as they did indeed remove the subpixel antialiasing. Thankfully, the Gtk4 developers removed it as well, so Linux (or at least the GNOME desktop) will soon look like crap as well.

jahewson
0 replies
5d19h

Font hinting is still used, as the native resolution of most TTFs is 2048 upem so even at 72pt you’d need a 2048 dpi display to perfectly render a glyph.

You’re right that sub-pixel (color) antialiasing has been removed from macOS but regular grayscale antialiasing, and hinting, remain.

jahewson
5 replies
5d19h

Great article, I’ve had the fun of implementing TTF rendering and this piece hits all the memorable points.

One nit, the comparison to Mach-O is anachronistic as TrueType was developed in the late 1980s, while Apple didn’t acquire NeXT until 1996.

Allybag
2 replies
5d18h

That’s not really a fair nit at all, he just commented that the two formats were similar, one of them must necessarily have come before the other, but they both exist right now.

jahewson
1 replies
5d17h

I don’t agree with that, the quote is:

Both file formats were developed at Apple around the same time
Allybag
0 replies
5d12h

Ah, indeed, sorry about that. I’d missed that part and just remembered him saying they were similar.

mannyv
0 replies
5d14h

TT is just a tagged file format, just like WAV, TIFF, MOV, MP4, etc.

It's probably closer to quicktime, which are the basis of mp4.

codyd51
0 replies
5d6h

Thank you for the correction! I suspected I might have been off here. I've gone ahead and updated the article.

082349872349872
3 replies
5d22h

For a problem that is theoretically binary (either in, or out?) filling polys can have really fiddly corner (literally!) cases.

Thanks for the WIP screenshots; they made me feel much better about my own slicers/rasterisers.

(there's probably a trick I'm missing*, but so far I've had the most robust results for 3D booleans by chasing fiddly overlaps/intersections on lower facets, all the way down to 0-D if necessary)

* like working directly with cubics, à la Jim Blinn?

nsajko
0 replies
5d20h

For a problem that is theoretically binary (either in, or out?) filling polys can have really fiddly corner (literally!) cases.

FTR, in case someone is interested: "predicate" is the name for functions that output booleans, so a more general search term is "geometric predicate".

kevingadd
0 replies
5d22h

Filling polys is especially hard if you want to run on the GPU where access to denormals is inconsistent/nonexistent. Most of the algorithms in CS literature simply won't work. It's a fun challenge, at least!

codyd51
0 replies
5d6h

It definitely surprised me with how fiddly it was to get working! Thank you for commenting!

tomduncalf
2 replies
5d10h

Fascinating! I created a tool to parse information out of an OTF for uploading new fonts to a font foundry website so already had some idea of how complex they are (even with the help of “fonttools”, which is a great way to explore to the various tables), but I had no idea about some of this - for example, the VM for hinting!

slimsag
0 replies
5d10h

Modern text shaping can even require executing a WASM binary in the font file[0]

[0] https://news.ycombinator.com/item?id=36652569

codyd51
0 replies
5d6h

Thank you very much!

neontomo
2 replies
5d16h

Can someone explain how connecting the dots works? How does it know which dot is connected to which? For example, in the curve of an S, the closest dot to the top curve might be under it, instead of beside it.

doubloon
0 replies
5d12h

The points are a sequence of integer coordinates. The points in the outside contour of the letter are stored in clockwise order, the inner holes in counter clockwise order. There is some kind of marker to indicate where each countiur starts. The points are either on curve or off curve. On curve are touching the visible line, off curve point is a control point of a three-point quadratic Bezier curve. If there are two offcurve points listed one after the other then you create an implied oncurve point halfway between them.

TheCoreh
0 replies
5d13h

The dots are stored in the right order in the font. The curves that connect them are also precisely specified

vardump
1 replies
5d21h

Wrote a truetype rasterizer about 20 years ago. I took the easy way out and just simply antialised everything, didn't even try to support TrueType hinting programs.

Sure, things looked blurry, but it didn't matter much when everything would be rotated into weird angles and sizes anyways.

Bezier and implementing correct and FAST filling rules took a lot of effort.

djmips
0 replies
5d18h

What was your application? Games?

signaru
1 replies
5d22h

Great to see more accessible references on font internals! I have dabbled on this a bit last year and managed to have a parser and render the points in a glyph's contour (I stopped before Bezier and shape filling stuff). I still have not considered hinting, so it's nice that it's covered. Besides Apple's and Microsoft's extensive documentations, what really helped me is an article from the Handmade Network [1] and the source of stb_truetype [2] (also used in Dear ImGUI).

[1] https://handmade.network/forums/articles/t/7330-implementing....

[2] https://github.com/nothings/stb/blob/master/stb_truetype.h

pekim
0 replies
4d6h

Sean Barrett, the author of stb_truetype.h, has an interesting article about how stb_truetype does its glyph rasterisation.

https://nothings.org/gamedev/rasterize/

kevingadd
1 replies
5d22h

I really appreciate that you provided a bunch of screenshots of the in-progress development. Graphics programming is very challenging, and it can be frustrating to feel like you've spent multiple days just rendering glitch art. It's always good to show others how much we struggle even if we're experts :)

Having recently implemented a subset of opentype kerning, I can only imagine how stressful it was to implement the whole core truetype spec...

codyd51
0 replies
5d6h

That's really kind feedback, thank you! I'm glad people seem to be enjoying them.

It's also reiterated the importance for me to take screenshots as I go. For many/most projects, these intermediary and bugged out results are gone as quickly as they come if I don't make an effort to persist them.

sokoloff
0 replies
5d17h

The first startup I worked at was a TrueType font specialist. We had one of the best auto-hinters and decent manual tooling for improving the auto-hinting.

Ultimately, we ran out of traction and money as the CDs full of 100 fonts for $10 came out, but we did some of the original System 7 and Win 3.1 fonts.

It is a cool format and stack-based language and was an enjoyable and educational time in my career.

mdaniel
0 replies
5d15h

I couldn't readily tell if this was related to the recent 37C3 iPhone submission, but in case someone missed the irony: https://securelist.com/operation-triangulation-the-last-hard... was RCE-ed via a truetype file and it's always been just the gift that keeps on giving https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=truetype

cbrpnk
0 replies
5d20h

"There’s few things in this world I love more than taking an opaque binary and gradually uncovering the organization that was present all along. Dissecting the underlying and general structure of a binary is as gratifying as anything I can imagine."

Yes!