<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Font Rendering on Jefferson Oliveira</title><link>https://jeffersonmourak.com/tags/font-rendering/</link><description>Recent content in Font Rendering on Jefferson Oliveira</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>All content provided by this website are my own opinons and beliefs and are distrubuted under the &lt;br/></copyright><lastBuildDate>Thu, 31 Jul 2025 10:29:04 -0300</lastBuildDate><atom:link href="https://jeffersonmourak.com/tags/font-rendering/index.xml" rel="self" type="application/rss+xml"/><item><title>A Tela é o Quadro - Desenhando fontes com matemática</title><link>https://jeffersonmourak.com/blog/font-rendering/</link><pubDate>Thu, 31 Jul 2025 10:29:04 -0300</pubDate><guid>https://jeffersonmourak.com/blog/font-rendering/</guid><category domain="https://jeffersonmourak.com/tags/font-rendering/">Font Rendering</category><category domain="https://jeffersonmourak.com/tags/font/">Font</category><category domain="https://jeffersonmourak.com/tags/computer-graphics/">Computer Graphics</category><description>&lt;p>No sábado, 26 de julho de 2025, apresentei uma palestra sobre renderização de fontes no Google I/O Extended Natal. Devido à correria do dia a dia, não consegui mostrar muitos exemplos práticos interativos. Este artigo serve justamente para isso: vamos explorar um pouco sobre como o texto que você está lendo é formado na sua tela.&lt;/p>
&lt;p>Para começar, falaremos sobre Bitmaps. Esta é a forma mais ingênua de desenhar fontes, pois um bitmap nada mais é do que uma imagem pronta. Abaixo, por exemplo, temos uma letra que ocupa um espaço de 6 pixels de altura por 6 pixels de largura.&lt;/p></description><content:encoded><![CDATA[<p>No sábado, 26 de julho de 2025, apresentei uma palestra sobre renderização de fontes no Google I/O Extended Natal. Devido à correria do dia a dia, não consegui mostrar muitos exemplos práticos interativos. Este artigo serve justamente para isso: vamos explorar um pouco sobre como o texto que você está lendo é formado na sua tela.</p>
<p>Para começar, falaremos sobre Bitmaps. Esta é a forma mais ingênua de desenhar fontes, pois um bitmap nada mais é do que uma imagem pronta. Abaixo, por exemplo, temos uma letra que ocupa um espaço de 6 pixels de altura por 6 pixels de largura.</p>
<p>No momento, você consegue visualizar facilmente porque o tamanho dos pixels está em 10 pixels. No entanto, se você alterar o tamanho para 1 pixel, verá que não é possível ler o que está na tela.&quot;</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<p>Como mostrado, o maior problema das fontes de bitmap é que elas não são escaláveis. Ou seja, para que possamos mudar o tamanho das fontes, teríamos que:</p>
<ul>
<li>
<p>Criar uma nova fonte com cada uma das letras desenhadas no novo tamanho.</p>
</li>
<li>
<p>Rasterizar a fonte em uma outra escala.</p>
</li>
</ul>
<p>Vamos ver o que acontece na segunda opção.</p>
<p>Essa é uma função simples de escala. Ela recebe como parâmetros os dados da letra que você quer desenhar e a escala na qual deseja aumentar.</p>
<p>Ela funciona simplesmente duplicando os pixels existentes tanto no eixo <em>X</em> quanto no <em>Y</em>.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">scaleGlyph</span> (<span style="color:#a6e22e">glyph</span>, <span style="color:#a6e22e">scale</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newWidth</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">ceil</span>(<span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">width</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newHeight</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">ceil</span>(<span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">height</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newPixels</span> <span style="color:#f92672">=</span> Array.<span style="color:#a6e22e">from</span>({ <span style="color:#a6e22e">length</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newHeight</span> }, () =&gt;
</span></span><span style="display:flex;"><span>    Array(<span style="color:#a6e22e">newWidth</span>).<span style="color:#a6e22e">fill</span>(<span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">y</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">y</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">height</span>; <span style="color:#a6e22e">y</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">x</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">x</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">width</span>; <span style="color:#a6e22e">x</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">pixels</span>[<span style="color:#a6e22e">y</span>][<span style="color:#a6e22e">x</span>];
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newX</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">floor</span>(<span style="color:#a6e22e">x</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newY</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">floor</span>(<span style="color:#a6e22e">y</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">dy</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">dy</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">scale</span>; <span style="color:#a6e22e">dy</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">dx</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">dx</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">scale</span>; <span style="color:#a6e22e">dx</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">newY</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dy</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">newHeight</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">newX</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dx</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">newWidth</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">newPixels</span>[<span style="color:#a6e22e">newY</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dy</span>][<span style="color:#a6e22e">newX</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dx</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">value</span>;
</span></span><span style="display:flex;"><span>          }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">width</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newWidth</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">height</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newHeight</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">pixels</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newPixels</span>,
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>O problema desse tipo de escala é que as fontes acabam obtendo um aspecto de bloco, o que traz a sensação de uma imagem com baixa resolução.</p>
<p>Outra forma é aplicando uma escala utilizando interpolação linear. Essa técnica consiste em tirar uma média de todos os pontos originais ao redor, em vez de simplesmente copiar o bloco inteiro, repetindo cegamente o que há no pixel. No entanto, isso agora resulta em um aspecto de imagem borrada, e essa característica se acentua quanto maior a diferença entre o tamanho original e o tamanho final.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">x0</span>, <span style="color:#a6e22e">v0</span>, <span style="color:#a6e22e">x1</span>, <span style="color:#a6e22e">v1</span>, <span style="color:#a6e22e">x</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">x0</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">x1</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">v0</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">v0</span> <span style="color:#f92672">+</span> (<span style="color:#a6e22e">v1</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">v0</span>) <span style="color:#f92672">*</span> ((<span style="color:#a6e22e">x</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">x0</span>) <span style="color:#f92672">/</span> (<span style="color:#a6e22e">x1</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">x0</span>));
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">bilinearInterpolate</span>(<span style="color:#a6e22e">Q11</span>, <span style="color:#a6e22e">Q21</span>, <span style="color:#a6e22e">Q12</span>, <span style="color:#a6e22e">Q22</span>, <span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q22</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">y</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">y</span> <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">y</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q22</span>.<span style="color:#a6e22e">y</span>
</span></span><span style="display:flex;"><span>  ) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">error</span>(
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;Error: The provided points do not form a proper rectangle for bilinear interpolation.&#34;</span>
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">x1</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">x</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">x2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">x</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">y1</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">y</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">y2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">y</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">R1</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">x1</span>, <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x2</span>, <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">R2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">x1</span>, <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x2</span>, <span style="color:#a6e22e">Q22</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">P</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">y1</span>, <span style="color:#a6e22e">R1</span>, <span style="color:#a6e22e">y2</span>, <span style="color:#a6e22e">R2</span>, <span style="color:#a6e22e">y</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">P</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Com isso temos os exemplos a baixo,</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<h2 id="como-utilizar-uma-só-fonte-para-vários-tamanhos">Como utilizar uma só fonte para vários tamanhos?</h2>
<p>Na matemática, existem equações que desenham um gráfico na tela. Os exemplos mais comuns são:</p>
<h3 id="função-quadrática">Função quadrática</h3>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<h3 id="função-inversa-multiplicativa">Função inversa multiplicativa</h3>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<h3 id="manipulando-a-curva">Manipulando a curva</h3>
<p>Para mover nossas equações, podemos somar um valor qualquer após o resultado da exponenciação e, assim, movemos nossa equação no eixo <em>Y</em>.</p>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<p>Para mover nossa equação na horizontal, adicionamos esse valor antes de elevá-lo ao quadrado.</p>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<p>Então, já temos uma maneira de representar nossas curvas utilizando equações matemáticas.</p>
<p>Mas antes de desenharmos, vamos aprender sobre mais uma coisa.</p>
<h2 id="curvas-de-bézier">Curvas de Bézier</h2>
<p>Ela é uma curva polinomial expressa como a interpolação linear entre alguns pontos representativos, chamados de pontos de controle.</p>
<p>No exemplo abaixo, temos 3 pontos: <em>P0</em>, <em>P1</em> e <em>P2</em>, onde <em>P0</em> e <em>P2</em> são os pontos representativos e <em>P1</em> é o ponto de controle, você pode mover os exemplos abaixo e ver o resultado.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<h3 id="desenhando-uma-letra-com-vetores">Desenhando uma letra com vetores</h3>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<p>Com o conceito de Bézier, fica até intuitivo como podemos desenhar uma letra usando matemática: basta organizar pontos em sequência e misturar linhas retas com curvas de Bézier, fazendo com que o <em>P2</em> de uma termine exatamente onde começa o <em>P0</em> da outra.</p>
<p>Aliás, uma reta também pode ser feita com Bézier; basta alinhar todos os pontos. Dessa forma, fica ainda mais claro como a interpolação atua na curva de Bézier.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<p>Com isso, já podemos agora pensar em como transformar isso em um bitmap. Para fazer isso, precisamos primeiramente rasterizar essa fonte, começando por traduzir as curvas de Bézier em linhas compatíveis com a resolução da tela. Isso acontece porque a tela do computador é uma matriz de pixels; logo, precisamos transformar curvas em pixels legíveis ao olho humano.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<p>Feito isso, a última coisa que se precisa é preencher a letra. Essa parte pode ser feita por um processo chamado scanline, que consiste em lançar um raio e contar quantas vezes esse raio vai tocar uma das paredes da letra. Se o número de toques for par, o pixel está representado fora da letra; se for ímpar, ele está dentro.</p>
<div class="figure-row">
  <figure>
      <img src="outter-intersect.png" alt="2 intersections with the letter">
      <figcaption>2 Interseções com a letra</figcaption>
  </figure>
  <figure>
      <img src="in-vs-out.png" alt="2 and 3 intersections with the letter">
      <figcaption>2 e 3 Interseções com a letra</figcaption>
  </figure>
  <figure>
      <img src="full-example.png" alt="2, 3, and 4 intersections with the letter">
      <figcaption>2, 3 e 4 Interseções com a letra</figcaption>
  </figure>
</div>
<p>Perceba que, no exemplo da letra &lsquo;O&rsquo;, há uma falha na renderização. Ela está aí de propósito: o processo de renderizar fontes é complicado e cheio de edge cases que só aumentam quanto mais aprofundamos no assunto.</p>
<p>O que quero demonstrar com essa falha é que, além de contar quantas vezes sua linha corta a letra, deve-se também estar ciente se a linha está cortando ela mesma novamente.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering/">view it on the blog</a>.</em></p>
<p>Bem, e com isso, concluímos esta etapa do processo de renderização das fontes. Daqui a uns dias, vou publicar outros dois artigos sobre o tema para complementar o assunto da palestra. Eles serão sobre Unicode e Text Shaping.</p>
<p>Muito obrigado, e até a próxima! 😊</p>
<figure>
  <img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdXM1amV6MWJ0eWs4cjY3OHJhNXNwZ2o1ZWkzYnVrNjMxZTlwNmlpcyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/1jkVi22T6iUrQJUNqk/giphy.gif" alt="Jimmy Fallon escorregando e dando tchau" loading="lazy">
  <figcaption>Jimmy Fallon escorregando e dando tchau</figcaption>
</figure><h2 id="referencias">Referencias</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=qcMuyHzhvpI">A Brief look at Text Rendering - VoxelRifts (YouTube)</a></li>
<li><a href="https://www.youtube.com/watch?v=SO83KQuuZvg">Coding Adventure: Rendering Text -Sebastian Lague (YouTube)</a></li>
<li><a href="https://www.youtube.com/watch?v=LaYPoMPRSlk">The Math Behind Font Rasterization | How it Works - GamesWithGame (YouTube)</a></li>
<li><a href="https://faultlore.com/blah/text-hates-you/">Text Rendering Hates You - Aria Desires</a></li>
<li><a href="https://github.com/Chlumsky/msdfgen">Multi-channel signed distance field generator - Viktor Chlumský[Valve] (GitHub)</a></li>
<li><a href="https://github.com/harfbuzz/harfbuzz">Harfbuzz[Google] - (GitHub)</a></li>
</ul>
<!--
  p5.js is loaded here as a plain global so the inline observable:notebook
  cells in this post (the `p5(sketch)` wrapper at id="0") can reference
  `window.p5` directly without going through Observable's `require()`. A
  non-deferred <script> blocks parsing where it sits, so by the time the
  notebook's module bundle runs (module scripts are deferred until the
  document finishes parsing), `window.p5` is guaranteed to exist.
-->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
]]></content:encoded></item><item><title>The Screen is the Canvas - Drawing Fonts with Mathematics</title><link>https://jeffersonmourak.com/blog/font-rendering-en/</link><pubDate>Thu, 31 Jul 2025 10:29:04 -0200</pubDate><guid>https://jeffersonmourak.com/blog/font-rendering-en/</guid><category domain="https://jeffersonmourak.com/tags/font-rendering/">Font Rendering</category><category domain="https://jeffersonmourak.com/tags/font/">Font</category><category domain="https://jeffersonmourak.com/tags/computer-graphics/">Computer Graphics</category><description>&lt;blockquote>
&lt;p>Hey Y&amp;rsquo;all! This is a translation of my blog post originally written in Portuguese.&lt;/p>&lt;/blockquote>
&lt;p>On Saturday, July 26, 2025, I presented a lecture on font rendering at Google I/O Extended Natal. Due to the rush of daily life, I couldn&amp;rsquo;t show many interactive practical examples. This article serves exactly that purpose: let&amp;rsquo;s explore a bit about how the text you&amp;rsquo;re reading is formed on your screen.&lt;/p>
&lt;p>To start, we&amp;rsquo;ll talk about Bitmaps. This is the most naive way to draw fonts, as a bitmap is nothing more than a ready-made image. Below, for example, we have a letter that occupies a space of 6 pixels in height by 6 pixels in width.&lt;/p></description><content:encoded><![CDATA[<blockquote>
<p>Hey Y&rsquo;all! This is a translation of my blog post originally written in Portuguese.</p></blockquote>
<p>On Saturday, July 26, 2025, I presented a lecture on font rendering at Google I/O Extended Natal. Due to the rush of daily life, I couldn&rsquo;t show many interactive practical examples. This article serves exactly that purpose: let&rsquo;s explore a bit about how the text you&rsquo;re reading is formed on your screen.</p>
<p>To start, we&rsquo;ll talk about Bitmaps. This is the most naive way to draw fonts, as a bitmap is nothing more than a ready-made image. Below, for example, we have a letter that occupies a space of 6 pixels in height by 6 pixels in width.</p>
<p>At the moment, you can easily visualize it because the pixel size is set to 10 pixels. However, if you change the size to 1 pixel, you&rsquo;ll see that it&rsquo;s not possible to read what&rsquo;s on the screen.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<p>As shown, the biggest problem with bitmap fonts is that they are not scalable. That is, to change the font size, we would have to:</p>
<ul>
<li>
<p>Create a new font with each letter drawn in the new size.</p>
</li>
<li>
<p>Rasterize the font at another scale.</p>
</li>
</ul>
<p>Let&rsquo;s see what happens in the second option.</p>
<p>This is a simple scaling function. It receives as parameters the data of the letter you want to draw and the scale at which you want to increase it.</p>
<p>It works simply by duplicating existing pixels on both the <em>X</em> and <em>Y</em> axes.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">scaleGlyph</span> (<span style="color:#a6e22e">glyph</span>, <span style="color:#a6e22e">scale</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newWidth</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">ceil</span>(<span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">width</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newHeight</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">ceil</span>(<span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">height</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newPixels</span> <span style="color:#f92672">=</span> Array.<span style="color:#a6e22e">from</span>({ <span style="color:#a6e22e">length</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newHeight</span> }, () =&gt;
</span></span><span style="display:flex;"><span>    Array(<span style="color:#a6e22e">newWidth</span>).<span style="color:#a6e22e">fill</span>(<span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">y</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">y</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">height</span>; <span style="color:#a6e22e">y</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">x</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">x</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">width</span>; <span style="color:#a6e22e">x</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">value</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">glyph</span>.<span style="color:#a6e22e">pixels</span>[<span style="color:#a6e22e">y</span>][<span style="color:#a6e22e">x</span>];
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newX</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">floor</span>(<span style="color:#a6e22e">x</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">newY</span> <span style="color:#f92672">=</span> Math.<span style="color:#a6e22e">floor</span>(<span style="color:#a6e22e">y</span> <span style="color:#f92672">*</span> <span style="color:#a6e22e">scale</span>);
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">dy</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">dy</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">scale</span>; <span style="color:#a6e22e">dy</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">for</span> (<span style="color:#66d9ef">let</span> <span style="color:#a6e22e">dx</span> <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">dx</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">scale</span>; <span style="color:#a6e22e">dx</span><span style="color:#f92672">++</span>) {
</span></span><span style="display:flex;"><span>          <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">newY</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dy</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">newHeight</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">newX</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dx</span> <span style="color:#f92672">&lt;</span> <span style="color:#a6e22e">newWidth</span>) {
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">newPixels</span>[<span style="color:#a6e22e">newY</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dy</span>][<span style="color:#a6e22e">newX</span> <span style="color:#f92672">+</span> <span style="color:#a6e22e">dx</span>] <span style="color:#f92672">=</span> <span style="color:#a6e22e">value</span>;
</span></span><span style="display:flex;"><span>          }
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      }
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">width</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newWidth</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">height</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newHeight</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">pixels</span><span style="color:#f92672">:</span> <span style="color:#a6e22e">newPixels</span>,
</span></span><span style="display:flex;"><span>  };
</span></span><span style="display:flex;"><span>};
</span></span></code></pre></div><p>The problem with this type of scaling is that fonts end up getting a blocky appearance, which brings the feeling of a low-resolution image.</p>
<p>Another way is by applying scaling using linear interpolation. This technique consists of taking an average of all the original points around, instead of simply copying the entire block, blindly repeating what&rsquo;s in the pixel. However, this now results in a blurred image appearance, and this characteristic becomes more pronounced the greater the difference between the original size and the final size.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">x0</span>, <span style="color:#a6e22e">v0</span>, <span style="color:#a6e22e">x1</span>, <span style="color:#a6e22e">v1</span>, <span style="color:#a6e22e">x</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (<span style="color:#a6e22e">x0</span> <span style="color:#f92672">===</span> <span style="color:#a6e22e">x1</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">v0</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">v0</span> <span style="color:#f92672">+</span> (<span style="color:#a6e22e">v1</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">v0</span>) <span style="color:#f92672">*</span> ((<span style="color:#a6e22e">x</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">x0</span>) <span style="color:#f92672">/</span> (<span style="color:#a6e22e">x1</span> <span style="color:#f92672">-</span> <span style="color:#a6e22e">x0</span>));
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">bilinearInterpolate</span>(<span style="color:#a6e22e">Q11</span>, <span style="color:#a6e22e">Q21</span>, <span style="color:#a6e22e">Q12</span>, <span style="color:#a6e22e">Q22</span>, <span style="color:#a6e22e">x</span>, <span style="color:#a6e22e">y</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> (
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q22</span>.<span style="color:#a6e22e">x</span> <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">y</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">y</span> <span style="color:#f92672">||</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">y</span> <span style="color:#f92672">!==</span> <span style="color:#a6e22e">Q22</span>.<span style="color:#a6e22e">y</span>
</span></span><span style="display:flex;"><span>  ) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">console</span>.<span style="color:#a6e22e">error</span>(
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;Error: The provided points do not form a proper rectangle for bilinear interpolation.&#34;</span>
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">x1</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">x</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">x2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">x</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">y1</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">y</span>;
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">y2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">y</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">R1</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">x1</span>, <span style="color:#a6e22e">Q11</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x2</span>, <span style="color:#a6e22e">Q21</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">R2</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">x1</span>, <span style="color:#a6e22e">Q12</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x2</span>, <span style="color:#a6e22e">Q22</span>.<span style="color:#a6e22e">value</span>, <span style="color:#a6e22e">x</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">P</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">lerp</span>(<span style="color:#a6e22e">y1</span>, <span style="color:#a6e22e">R1</span>, <span style="color:#a6e22e">y2</span>, <span style="color:#a6e22e">R2</span>, <span style="color:#a6e22e">y</span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">P</span>;
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>With this we have the examples below:</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<h2 id="how-to-use-one-font-for-multiple-sizes">How to use one font for multiple sizes?</h2>
<p>In mathematics, there are equations that draw a graph on the screen. The most common examples are:</p>
<h3 id="quadratic-function">Quadratic function</h3>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<h3 id="multiplicative-inverse-function">Multiplicative inverse function</h3>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<h3 id="manipulating-the-curve">Manipulating the curve</h3>
<p>To move our equations, we can add any value after the result of the exponentiation, and thus we move our equation on the <em>Y</em> axis.</p>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<p>To move our equation horizontally, we add that value before squaring it.</p>
<p><em>Interactive Observable notebook widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<p>So, we already have a way to represent our curves using mathematical equations.</p>
<p>But before we draw, let&rsquo;s learn about one more thing.</p>
<h2 id="bézier-curves">Bézier curves</h2>
<p>It&rsquo;s a polynomial curve expressed as the linear interpolation between some representative points, called control points.</p>
<p>In the example below, we have 3 points: <em>P0</em>, <em>P1</em> and <em>P2</em>, where <em>P0</em> and <em>P2</em> are the representative points and <em>P1</em> is the control point, you can move the examples below and see the result.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<h3 id="drawing-a-letter-with-vectors">Drawing a letter with vectors</h3>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<p>With the Bézier concept, it becomes quite intuitive how we can draw a letter using mathematics: just organize points in sequence and mix straight lines with Bézier curves, making the <em>P2</em> of one end exactly where the <em>P0</em> of the other begins.</p>
<p>By the way, a straight line can also be made with Bézier; just align all the points. This way, it becomes even clearer how interpolation acts on the Bézier curve.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<p>With this, we can now think about how to transform this into a bitmap. To do this, we first need to rasterize this font, starting by translating the Bézier curves into lines compatible with the screen resolution. This happens because the computer screen is a matrix of pixels; therefore, we need to transform curves into pixels readable to the human eye.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<p>Once this is done, the last thing needed is to fill the letter. This part can be done by a process called scanline, which consists of launching a ray and counting how many times that ray will touch one of the walls of the letter. If the number of touches is even, the pixel is represented outside the letter; if it&rsquo;s odd, it&rsquo;s inside.</p>
<div class="figure-row">
  <figure>
      <img src="outter-intersect.png" alt="2 intersections with the letter">
      <figcaption>2 intersections with the letter</figcaption>
  </figure>
  <figure>
      <img src="in-vs-out.png" alt="2 and 3 intersections with the letter">
      <figcaption>2 and 3 intersections with the letter</figcaption>
  </figure>
  <figure>
      <img src="full-example.png" alt="2, 3, and 4 intersections with the letter">
      <figcaption>2, 3, and 4 intersections with the letter</figcaption>
  </figure>
</div>
<p>Notice that in the example of the letter &lsquo;O&rsquo;, there&rsquo;s a rendering flaw. It&rsquo;s there on purpose: the process of rendering fonts is complicated and full of edge cases that only increase the more we delve into the subject.</p>
<p>What I want to demonstrate with this flaw is that, besides counting how many times your line cuts the letter, you should also be aware if the line is cutting itself again.</p>
<p><em>Interactive p5 widget &mdash; <a href="https://jeffersonmourak.com/blog/font-rendering-en/">view it on the blog</a>.</em></p>
<p>Well, and with this, we conclude this stage of the font rendering process. In a few days, I&rsquo;ll publish two more articles on the topic to complement the lecture subject. They will be about Unicode and Text Shaping.</p>
<p>Thank you very much, and see you next time! 😊</p>
<figure>
  <img src="https://media2.giphy.com/media/v1.Y2lkPTc5MGI3NjExdXM1amV6MWJ0eWs4cjY3OHJhNXNwZ2o1ZWkzYnVrNjMxZTlwNmlpcyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/1jkVi22T6iUrQJUNqk/giphy.gif" alt="Jimmy Fallon sliding and waiving bye" loading="lazy">
  <figcaption>Jimmy Fallon sliding and waiving bye</figcaption>
</figure><h2 id="references">References</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=qcMuyHzhvpI">A Brief look at Text Rendering - VoxelRifts (YouTube)</a></li>
<li><a href="https://www.youtube.com/watch?v=SO83KQuuZvg">Coding Adventure: Rendering Text -Sebastian Lague (YouTube)</a></li>
<li><a href="https://www.youtube.com/watch?v=LaYPoMPRSlk">The Math Behind Font Rasterization | How it Works - GamesWithGame (YouTube)</a></li>
<li><a href="https://faultlore.com/blah/text-hates-you/">Text Rendering Hates You - Aria Desires</a></li>
<li><a href="https://github.com/Chlumsky/msdfgen">Multi-channel signed distance field generator - Viktor Chlumský[Valve] (GitHub)</a></li>
<li><a href="https://github.com/harfbuzz/harfbuzz">Harfbuzz[Google] - (GitHub)</a></li>
</ul>
<!--
  p5.js is loaded here as a plain global so the inline `p5` codeblocks
  on this page can reference `window.p5` directly. A non-deferred
  <script> blocks parsing where it sits, so by the time any inline
  module bundle runs, `window.p5` is guaranteed to exist.
-->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
]]></content:encoded></item></channel></rss>