8 ms·
Writing a TrueType font renderer
Hi HN, happy new year!
TrueType is really a neat and fun format, packed with esoterica. This post gives some background on the problem of text rendering in general, explains how TrueType works under the hood, and contains lots of tidbits that I hope will be interesting.
There’s also a bunch of screenshots of the in-progress renderer stumbling over itself. I hope you enjoy the read!
- tomduncalf 3y agoFascinating! 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!
- deleted 3y ago[deleted]
- signaru 3y agoGreat 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_a_font_reader_and_rasterizer_from_scratch%252C_part_1__ttf_font_reader https://handmade.network/forums/articles/t/7330-implementing.... [2] https://github.com/nothings/stb/blob/master/stb_truetype.h https://github.com/nothings/stb/blob/master/stb_truetype.h
- pekim 3y agoSean Barrett, the author of stb_truetype.h, has an interesting article about how stb_truetype does its glyph rasterisation. https://nothings.org/gamedev/rasterize/ https://nothings.org/gamedev/rasterize/
- 082349872349872 3y agoFor 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?
- codyd51 3y agoIt definitely surprised me with how fiddly it was to get working! Thank you for commenting!
- kevingadd 3y agoFilling 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!
- nsajko 3y ago> 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 3y agoI 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 3y agoThat'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.
- vardump 3y agoWrote 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 3y agoWhat was your application? Games?
- cbrpnk 3y ago"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!
- ianlevesque 3y ago> Inexplicably, though, the TTF stores all fields in big endian Both the 68k and PPC (as configured for macs) were big endian.
- codyd51 3y agoOf course, this makes sense - thank you!
- mannyv 3y agoLittle 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 3y agoVAX and PDP-11 were also little-endian.
- mark-r 3y agoWere those the first byte-addressable architectures? If they were you think they'd have had a bigger influence.
- peterfirefly 3y agoNo. They were both enormously influential, though. IBM S/360 got there before the PDP-11 (1964 vs 1970) -- but it probably also wasn't the first. https://en.wikipedia.org/wiki/IBM_System/360 https://en.wikipedia.org/wiki/IBM_System/360 Unix was developed on a PDP-11 (I ignore the first, embryonic, version on an 18-bit PDP-7 because that version was in assembler and couldn't really do much more than host Space War). The real, grown-up, version of Unix with proper virtual memory was developed on a VAX (as part of BSD Unix). Sockets also came from BSD Unix, developed on VAX. IEEE floating-point is largely based on their floating-point format -- but it is incompatible and has a number of extra bells and whistles that William Kahan insisted on. They were also used for developing various experimental programming languages. Windows NT is in many ways a reimplementation of DEC's VMS operating system for the VAX. The PDP-11/74 was an early experimental 4-way SMP machine -- that quickly taught the OS developers that they didn't know as much as they thought about how to write SMP software!
- vitiral 3y agoI 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.
- vidarh 3y agolibschrift is ~1500 lines of C: https://github.com/tomolt/libschrift https://github.com/tomolt/libschrift My Ruby rewrite is ~600 lines: https://github.com/vidarh/skrift 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 https://news.ycombinator.com/item?id=38839114
- stravant 3y agoYou'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.
- colonwqbang 3y agoUsing a better, higher-resolution bitmap font? They don't have to look as awful as that https://terminus-font.sourceforge.net/img1/12x24b.gif https://terminus-font.sourceforge.net/img1/12x24b.gif
- userbinator 3y agoI 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.
- txdv 3y agolink please
- Sharlin 3y agoWell, yes, but GDI isn't exactly available when you're writing your own OS like the author of the article.
- vidarh 3y agoThe 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.
- vidarh 3y ago400 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 3y agoIt'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 3y agoIs it open source? Would love to see. Doesn't matter if it's lacking error checks - still impressively small, and doubly so for C.
- hgs3 3y agoCorrect 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.
- grishka 3y ago> 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 3y ago> 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 3y agoFont 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.
- vidarh 3y ago"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 https://github.com/tomolt/libschrift [2] https://github.com/vidarh/skrift https://github.com/vidarh/skrift - X11 integration in https://github.com/vidarh/skrift-x11 https://github.com/vidarh/skrift-x11
- jahewson 3y agoGreat 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.
- codyd51 3y agoThank you for the correction! I suspected I might have been off here. I've gone ahead and updated the article.
- Allybag 3y agoThat’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.
- mannyv 3y agoTT 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.
- darknavi 3y agoWorking 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!
- manchmalscott 3y agoGodot 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.
- rijoja 3y agoThe 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?
- omnimus 3y agoTo 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 3y agoPostScript fonts exist and PostScript is a Turing-complete language. Source code is copyrightable, so this tracks
- xyzzy_plugh 3y agoRoughly 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.
- mdaniel 3y agorelevant: https://faultlore.com/blah/text-hates-you/ 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&sort=byPopularity&type=story 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
- sokoloff 3y agoThe 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.
- neontomo 3y agoCan 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.
- TheCoreh 3y agoThe dots are stored in the right order in the font. The curves that connect them are also precisely specified
- doubloon 3y agoThe 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.
- mdaniel 3y agoI 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-hardware-mystery/111669/ 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 https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=truetype