Remix.run Logo
Font Rendering from First Principles(mccloskeybr.com)
135 points by krapp 6 days ago | 19 comments
AxiomLab 5 hours ago | parent | next [-]

Fascinating read. Font rendering perfectly encapsulates the conflict between continuous mathematical curves and discrete pixel grids.

I run into similar 'quantization' challenges when building generative design systems in Python. Sometimes a mathematically 'perfect' alignment on the grid looks optically wrong to the human eye. The anti-aliasing logic described here is a great mental model for handling those edge cases.

gethly 7 minutes ago | parent | prev | next [-]

I'm working on my own text editor and have ventured into font rendering as well. The main thing to understand about fonts and font rendering is that they are just bitmap images and the program just puts them together with simple XY+WH from a pre-rendered square image(square because GPUs like squares), called atlas, which in CSS would be called a sprite. It's really that simple.

oxonia 8 hours ago | parent | prev | next [-]

Too long an article (about type!) to be in white monospace text on a black background.

olivia-banks 7 hours ago | parent | next [-]

I had to use a reader view extension to stand it ;-)

layer8 4 hours ago | parent [-]

Safari Reader View doesn’t support the site, so I backed out. Too monospaced; didn’t read.

brendamn 4 hours ago | parent | prev | next [-]

Hiding the scrollbar is the real crime here.

bob1029 6 hours ago | parent | prev | next [-]

It's the border that hurts my visual cortex.

NooneAtAll3 4 hours ago | parent | prev [-]

explain?

DavidPiper 4 hours ago | parent [-]

Not OP, but white text on black (especially at 100% contrast) is harder to read than black text on white. Monospace is harder to read than natural-width text. Large passages of text with both features is fatiguing to read.

NooneAtAll3 3 hours ago | parent [-]

> white text on black is harder to read than black text on white

not my experience (I prefer not to be flashbanged), but sure

DavidPiper 27 minutes ago | parent [-]

Different stroke (color) for different folk

skobes 8 hours ago | parent | prev | next [-]

Why is the whole implementation in header files?

csmantle 8 hours ago | parent | next [-]

Header-only libs can help avoiding the troubles and complexity of linker setup. This might be even more important on Windows, which this lib "explicitly support".

socalgal2 6 hours ago | parent | prev [-]

short answer, because C/C++ sucks. To work around how bad C/C++ sucks people put the entire implementation into one file. That way, there's less question of how you need to integrate it into your project.

In more modern langauges, this is a solved problem and it's easy to use other people code. In C/C++, it's not. As a relavant example, try using FreeType in your C/C++ project, make sure your solution compiles on Linxu, and Mac, and Windows (and ideally other platforms)

thegrim000 an hour ago | parent [-]

>> As a relavant example, try using FreeType in your C/C++ project, make sure your solution compiles on Linxu, and Mac, and Windows (and ideally other platforms)

find_package(Freetype REQUIRED)

target_link_libraries(myproject PRIVATE Freetype::Freetype)

_HMCB_ 8 hours ago | parent | prev | next [-]

In the comparisons, there’s no indication which is created with his/her rendering “engine.”

akoboldfrying 5 hours ago | parent | prev | next [-]

This was interesting, thanks. Was hoping to see a bit more about type hinting, but there's already a lot here.

A question about efficiency: IIUC, in your initial bitmap rastering implementation, you process a row of target bitmap pixels at once, accumulating a winding number count to know whether the pen should be up or down at each x position. It sounds like you are solving for t given the known x and y positions on every curve segment at every target pixel, and then checking whether t is in the valid range [0, 1). Is that right?

Because if so, I think you could avoid doing most of this computation by using an active edge list. Basically, in an initial step, compute bounds on the y extents of each curve segment -- upper bounds for the max y, lower bounds for the min y. (The max and min y values of all 3 points work fine for these, since a quadratic Bezier curve is fully inside the triangle they form.) For each of the two extents of each curve segment, add a (y position, reference to curve segment, isMin) triple to an array -- so twice as many array elements as curve segments. Then sort the array by y position. Now during the outer rendering loop that steps through increasing y positions, you can maintain an index in this list that steps forward whenever the next element crosses the new y value: Whenever this new element has isMin=true, add the corresponding curve segment to the set of "active segments" that you will solve for; whenever it's false, remove it from this set. This way, you never need to solve for t on the "inactive segments" that you know are bounded out on the y axis, which is probably most of them.

joshmarinacci 9 hours ago | parent | prev [-]

Hugged to death?

DiggyJohnson 8 hours ago | parent [-]

Worked for me 18:29ET