{"id":3063,"date":"2025-11-30T19:48:09","date_gmt":"2025-11-30T19:48:09","guid":{"rendered":"https:\/\/timallanwheeler.com\/blog\/?p=3063"},"modified":"2025-11-30T19:48:09","modified_gmt":"2025-11-30T19:48:09","slug":"a-truetype-font","status":"publish","type":"post","link":"https:\/\/timallanwheeler.com\/blog\/2025\/11\/30\/a-truetype-font\/","title":{"rendered":"A TrueType Font"},"content":{"rendered":"\n<style>\n@font-face {\n    font-family: 'TeSsElLaTe';\n    src: url('\/blog\/wp-content\/themes\/mog\/fonts\/tessellate_regular.ttf') format('truetype');\n    font-weight: normal;\n    font-style: normal;\n}\n.triangle-font-block {\n    font-family: 'TeSsElLaTe', sans-serif;\n    font-size: 96px;\n    line-height: 1;\n}\n.triangle-font-block-wrapper {\n    text-align: center;\n    width: 100%;\n    margin: 0 auto;\n}\n<\/style>\n\n<div class=\"triangle-font-block-wrapper\">\n    <div class=\"triangle-font-block\">\n        TrUeTyPe FoNt\n    <\/div>\n<\/div>\n\n\n\n<p>Early in 2025, I had stumbled on <a href=\"https:\/\/www.behance.net\/gallery\/8919821\/Modular-Typeface-Triangle-One\/modules\/66869653\">concept images of a font with triangular glyphs<\/a>. Something about it was appealing. I ended up crafting a similar concept, but extending it so that the equilateral triangles could tile together using both upward and downward facing triangles:<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"614\" height=\"213\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-24_19-11.png\" alt=\"\" class=\"wp-image-3064\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-24_19-11.png 614w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-24_19-11-300x104.png 300w\" sizes=\"auto, (max-width: 614px) 100vw, 614px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">The upward and downward facing glyphs for the triangle font, from A to Z.<\/p>\n\n\n\n<p>I was implementing OpenGL font rendering in C++, using <a href=\"https:\/\/github.com\/nothings\/stb\">Sean Barrett&#8217;s TrueType library<\/a> to load fonts and construct a font atlas. Somewhere in this whole process I decided it would be fun to implement a font I had conceptualized months ago for real, so to speak, as a TrueType font.<\/p>\n\n\n\n<p>TrueType doesn&#8217;t support conditional logic for alternating glyph directions like this, so in practice I achieve the effect by making all uppercase characters be upward facing triangles and all lowercase characters be downward facing triangles. The text below is &#8220;HeLlO wOrLd&#8221;:<\/p>\n\n\n\n<p><\/p>\n\n\n\n<div class=\"triangle-font-block-wrapper\">\n    <div class=\"triangle-font-block\">\n        HeLlO wOrLd\n    <\/div>\n<\/div>\n\n\n\n<p><\/p>\n\n\n\n<p>Having my own game that could load and use the font was helpful for debugging, once the binary was loadable, since I could step through it with a debugger. I also found <a href=\"https:\/\/fontdrop.info\/\">fontdrop<\/a> and the hex editor useful.<\/p>\n\n\n\n<p>You can <a href=\"\/blog\/wp-content\/themes\/mog\/fonts\/tessellate_regular.ttf\">download the font here<\/a>.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Architecture<\/h1>\n\n\n\n<p>There are two fundamental responsibilites: defining the font in memory and serializing it to <code>.ttf<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">FontDefinition font = CreateFont();\nbool success = ExportFont(&amp;font);<\/code><\/pre>\n\n\n\n<p>Separating font definition from binary export keeps the system reusable and avoids hard-coded constants leaking into the writer.<\/p>\n\n\n\n<p><code>CreateFont<\/code> populates a builder struct:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">FontDefinition font;\nassert(InitFontDefinition(&amp;font,\n    \/*units_per_em=*\/1000, \/*ascent=*\/866,\n    \/*descent=*\/0, \/*line_gap=*\/0,\n    \/*family_name=*\/\"TeSsElLaTe\",\n    \/*subfamily_name=*\/\"Regular\",\n    \/*unique_name=*\/\"TeSsElLaTe rEgUlAr v1.0\",\n    \/*full_name=*\/\"TeSsElLaTe rEgUlAr\")\n  &amp;&amp; \"Failed to initialize font\");\n\n\/\/ Create the missing glyph (.notdef) - a simple square\n\/\/ This glyph is shown when a character is not found in the font\nconst GlyphId missing_glyph = StartGlyph(&amp;font, \/*codepoint=*\/0, advance_width, left_side_bearing);\nassert(missing_glyph != 0xFFFF &amp;&amp; \"Failed to start missing glyph\");\n\n\/\/ Create a square contour with 4 points\nassert(StartContour(&amp;font) &amp;&amp; \"Failed to start contour for missing glyph\");\nassert(AddPoint(&amp;font, 0, 0, 1) &amp;&amp; \"Failed to add point 0\");\nassert(AddPoint(&amp;font, 1000, 0, 1) &amp;&amp; \"Failed to add point 1\");\nassert(AddPoint(&amp;font, 1000, 1000, 1) &amp;&amp; \"Failed to add point 2\");\nassert(AddPoint(&amp;font, 0, 1000, 1) &amp;&amp; \"Failed to add point 3\");\nassert(EndContour(&amp;font) &amp;&amp; \"Failed to end contour for missing glyph\");\nassert(EndGlyph(&amp;font) &amp;&amp; \"Failed to end missing glyph\");<\/code><\/pre>\n\n\n\n<p>The font definition is kept in a <code>truetype.hpp<\/code> header, along with builder helpers like <code>StartContour<\/code> and <code>AddPoint<\/code>. After defining all of the glyphs, we can also add kerning pairs. The default <code>xadvance<\/code> is set for subsequent equilateral triangles, and I reduce that for pairs that nest together.<\/p>\n\n\n\n<p><code>ExportFont<\/code> needs to open a file and write the binary <code>.ttf<\/code> file. The format consists of a directory listing the tables and their offsets, followed by the tables themselves, padded to 4-bytes. Each table has a checksum, and the data is exported as big-endian (not the default when using fwrite).<\/p>\n\n\n\n<p>I wanted to calculate the table checksums as I wrote to disk to avoid multiple passes over the data. This requirement was realized via a <code>TableWriter<\/code> struct along with some basic helper methods like <code>WriteU16BE(&amp;writer, value)<\/code>, which exports the value in big-endian and updates the checksum.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">struct TableWriter {\n    FILE* file;\n    u32 checksum;\n\n    u8 word_buffer[4];\n    u32 word_buffer_index; \/\/ 0-4\n};<\/code><\/pre>\n\n\n\n<p>All <code>WriteXXBE<\/code> helper methods call down to a <code>void FeedBytesToChecksum(TableWriter* writer, const u8* data, u32 size);<\/code> method, which appropriately updates the checksum and writes the data to the file.<\/p>\n\n\n\n<p>After writing placeholder values for the directory, the exporter writes all of the tables, in alphabetical order. Each table is written as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">\/\/ Write cmap table\nwriter.checksum = 0; \/\/ Reset.\nTableInfo* table_info = &amp;table_infos[table_index];\ntable_info-&gt;offset = (u32)ftell(writer.file);\ntable_info-&gt;length = WriteCmapTable(&amp;writer, font);\ntable_info-&gt;checksum = writer.checksum;\nPadTo4ByteAlignment(&amp;writer);\nprintf(\"Wrote %s table (%u bytes, offset %u, checksum 0x%08X)\\n\",\n    table_tags[table_index], table_info-&gt;length, table_info-&gt;offset, table_info-&gt;checksum);\ntable_index++;<\/code><\/pre>\n\n\n\n<p>Offset values are captured directly from the file stream as each table is written to disk, enabling a single streaming pass without rewinding or recomputing. Each table has its own <code>WriteXXXXTable<\/code> method, all of which were kept in a <code>truetype_export.hpp<\/code> header. This made it easy to iterate on the code.<\/p>\n\n\n\n<p>We then go back and update the table directory. Easy peasy.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Finishing a Vertical Slice<\/h1>\n\n\n\n<p>I didn&#8217;t know whether the font exporter was working until I was able to load the resulting <code>.ttf<\/code> file in another program. A vertical slice lets you de-risk binary exporters early. I prioritized this by not immediately defining all glyphs and instead just defined the undefined glyph (.notdef), space, and &#8216;A&#8217;. I implemented the minimum necessary set of TrueType tables that could be loaded by tools like <code>stb_truetype<\/code> and the browser inspector, helping me confirm correctness before scaling out to <code>A\u2013Z<\/code> and <code>a-z<\/code>.<\/p>\n\n\n\n<p>Having a testable vertical slice is essential when coding on any project, whether solo or on a team. Coding without knowing whether your code is working is the same as flying blind. A hex editor provides directional confidence that the structure is forming correctly, but real validation only comes when another program successfully loads the font.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Bugs<\/h1>\n\n\n\n<p>Sometimes it is interesting to look at what sorts of issues you run into. Understanding <em>how<\/em> a process fails tells you where to focus on improvements. The bugs that I spent time investigating were:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Glyph alignment (segfault). Each glyph must be word-aligned. IMO, this is not at all obvious from <a href=\"https:\/\/developer.apple.com\/fonts\/TrueType-Reference-Manual\/RM06\/Chap6glyf.html\">the glyf table documentation<\/a>.<\/li>\n\n\n\n<li>Malformed contours as a result of exporting glyph vertices rather than relative vertices. This was clearly laid out in the documentation.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"693\" height=\"297\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-30_10-32.png\" alt=\"\" class=\"wp-image-3105\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-30_10-32.png 693w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-30_10-32-300x129.png 300w\" sizes=\"auto, (max-width: 693px) 100vw, 693px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">Image from <a href=\"https:\/\/developer.apple.com\/fonts\/TrueType-Reference-Manual\/RM06\/Chap6glyf.html\">developer.apple.com<\/a>.<\/p>\n\n\n\n<p>That was enough to get it working with stb_truetype and my font rendering. In order to get the chrome console to be happy, I additionally had to:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fix the <code>cmap<\/code> length, which was miscalculated. Code like this is not ideal:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">\/\/ Calculate subtable length\n\/\/ Format 4 header: 14 bytes (format, length, language, segCountX2, searchRange, entrySelector, rangeShift)\n\/\/ Data: reservedPad (2) + 4 arrays of seg_count u16 values (seg_count * 8)\nconst u16 subtable_length = 14 + 2 + (seg_count * 8);<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Needing to additionally export <a href=\"https:\/\/developer.apple.com\/fonts\/TrueType-Reference-Manual\/RM06\/Chap6OS2.html\">the OS\/2 table<\/a>, which includes metrics needed by Windows. The chrome inspector straight up told me to add this.<\/li>\n\n\n\n<li>Table alignment (browser load failure). All tables must be 4-byte aligned. This was in the top-level docs:<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"706\" height=\"102\" src=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-30_10-43.png\" alt=\"\" class=\"wp-image-3107\" srcset=\"https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-30_10-43.png 706w, https:\/\/timallanwheeler.com\/blog\/wp-content\/uploads\/2025\/11\/2025-11-30_10-43-300x43.png 300w\" sizes=\"auto, (max-width: 706px) 100vw, 706px\" \/><\/figure>\n<\/div>\n\n\n<p class=\"has-text-align-center has-small-font-size\">Image from <a href=\"https:\/\/developer.apple.com\/fonts\/TrueType-Reference-Manual\/RM06\/Chap6.html\">apple.developer.com<\/a>. <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Lastly, kerning was working in my font rendering but not for all characters in chrome. It turned out that almost all of my glyphs start at (0,0), but a few, like &#8216;d&#8217;, did not. Chrome was rendering these further to the left. I had to set the left side bearing for those glyphs.<\/li>\n<\/ul>\n\n\n\n<p>So two counts of failing to use alignment. Seems like a trend I can be more aware of. Despite chasing binary alignment and metrics, I ran into no memory safety issues like null pointers or leaks \u2014 problems typically cited as risks of low-level code.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Initializer Lists<\/h1>\n\n\n\n<p>I am trying to write in a <a href=\"https:\/\/caseymuratori.com\/about\">Muratori<\/a>-inspired minimal C++ style. That is, C++ without a lot of the C++ features. Avoid classes, macros, templates, and the standard libraries. Why? Ostensibly because simpler code is sufficient, and then easier to understand and faster to compile \/ execute. Though doing it because I think it is interesting and I admire people like Casey and attitudes like <a href=\"https:\/\/nullprogram.com\/blog\/2023\/10\/08\/\">this one from Chris Wellons<\/a> is also a perfectly valid reason.<\/p>\n\n\n\n<p>I was able to author everything to adhere to this style, but found that I really did want to use initializer lists to simplify glyph creation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">assert(AddGlyph(&amp;font, 'A', advance_width, left_side_bearing,\n        {{A, D, N, O, E, B, C}, {S, L, R}}) &amp;&amp; \"Failed to add A glyph\");<\/code><\/pre>\n\n\n\n<p>I totally could do that without it:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code class=\"\">const GlyphId glyph = StartGlyph(&amp;font, 'A', advance_width, left_side_bearing);<br>assert(glyph != 0xFFFF &amp;&amp; \"Failed to start glyph\");<br><br>assert(StartContour(&amp;font) &amp;&amp; \"Failed to start contour\");<br>assert(AddPoint(&amp;font, font.units_per_em*A.x, font.units_per_em*A.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*D.x, font.units_per_em*D.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*N.x, font.units_per_em*N.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*O.x, font.units_per_em*O.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*E.x, font.units_per_em*E.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*B.x, font.units_per_em*B.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*C.x, font.units_per_em*C.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(EndContour(&amp;font) &amp;&amp; \"Failed to end contour\");<br><br>assert(StartContour(&amp;font) &amp;&amp; \"Failed to start contour\");<br>assert(AddPoint(&amp;font, font.units_per_em*S.x, font.units_per_em*S.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*L.x, font.units_per_em*L.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(AddPoint(&amp;font, font.units_per_em*R.x, font.units_per_em*R.y, 1) &amp;&amp; \"Failed to add point\");<br>assert(EndContour(&amp;font) &amp;&amp; \"Failed to end contour\");<br><br>assert(EndGlyph(&amp;font) &amp;&amp; \"Failed to end glyph\");<\/code><\/pre>\n\n\n\n<p>You can see why I wanted to save myself the typing.<\/p>\n\n\n\n<p>Unfortunately,  in addition to including <code>&lt;initializer_list<\/code>&gt;, I also ended up including <code>&lt;vector&gt;<\/code> because I was calling AddGlyph with a transform for all downward facing glyphs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"cpp\" class=\"language-cpp\">assert(AddGlyph(&amp;font, 'f', advance_width, left_side_bearing,\n        ReflectToDownwardGlyph({{A,B,F,J,N,U,V,S,G,C}}, t))\n        &amp;&amp; \"Failed to add f glyph\");<\/code><\/pre>\n\n\n\n<p> Unfortunately, I couldn&#8217;t have <code>ReflectToDownwardGlyph<\/code> modify an initializer list and produce a new one. Instead, I had to return an std::vector. Oh well.<\/p>\n\n\n\n<p>There probably are reasonable ways to do this in an minimal style. If you happen to know, please send me a message!<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p>It was fun to work on a self-contained, somewhat artistic project. I got a chance to try both the <a href=\"https:\/\/cursor.com\/agents\">Cursor<\/a> and <a href=\"https:\/\/antigravity.google\/\">Antigravity<\/a> agentic IDEs, both of which worked quite well. <\/p>\n\n\n\n<p>I&#8217;m not sure that I&#8217;ll be able to author posts monthly, but hopefully this is a good start to returning to my creative outlet. Happy Holidays!<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>TrUeTyPe FoNt Early in 2025, I had stumbled on concept images of a font with triangular glyphs. Something about it was appealing. I ended up crafting a similar concept, but extending it so that the equilateral triangles could tile together using both upward and downward facing triangles: The upward and downward facing glyphs for the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-3063","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3063","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/comments?post=3063"}],"version-history":[{"count":50,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3063\/revisions"}],"predecessor-version":[{"id":3123,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/posts\/3063\/revisions\/3123"}],"wp:attachment":[{"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/media?parent=3063"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/categories?post=3063"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timallanwheeler.com\/blog\/wp-json\/wp\/v2\/tags?post=3063"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}