How to proceed with fonts on OS X?

I've arrived at the fonts section: days 162 and so on. The intro with the STB library was fun, but starting from day 164 Casey starts to implement the Windows API version.

Since I'm building my version for OS X, I should either stick with STB or somehow do this with native SDK. Could you give me some hints on how would one approach the subject of font rasterisation using native APIs?
You need to use CoreGraphics and CoreText API.

First you would use CGBitmapContextCreate to create in-memory image.
And then draw to it using CTLineDraw (or similar) function.
You can use CTLineGetGlyphRuns (or similar) function to get character measurements.

Here's more complete example that renders text to memory image and saves it as jpeg: https://stackoverflow.com/a/11443795/675078

But there is nothing wrong to use stb_truetype. That's perfectly fine, and gives you cross-platform code and exactly same look for assets without depending on how specific platform is set up to render fonts.

Edited by Mārtiņš Možeiko on
Thanks, Mārtiņš!

This looks like a good start. I'll give it a go and see if I can come up with a similar result.
So, I've spent some time trying to get the same result out of STB library as shown with Windows API in day 169 video (aligning fonts to a baseline).

I feel like I have almost succeeded. I still have two questions:
1) does it look like a proper alpha blending to you? The fonts in the video seem cleaner.
2) how do I prevent a slight clipping on the left and bottom parts of the glyph bitmaps? See letter "u" or "e" for example. The letter "p" also has a small part of another glyph showing at the top.

I would appreciate some pointers, I can't figure out how to clip them properly.

Code for reference: https://gist.github.com/roman-d/4b70bcf0034f90c384b6d6bdcb587d7d

Screenshots:
fullscreen: https://gyazo.com/2fbdc01f037f140f928495445390afd9
Default window size: https://gyazo.com/449f6b1c0408f532dcf963d6364f2819

The letters are huge but that is because of the retina display. I generally downscale by 2.
1) It looks like the glyphs are created with smaller size and then rendered with bigger size, upscaled. Or is because whole handamde hero back buffer is 2x upscaled at the end?

2) probably wrong size/position calculations somewhere. Its easy to make mistake when, for example, rectangle is returned with left/right coordinates inclusive, but then you sometimes calculate width=right-left and loose one pixel right on the border. Or other similar calculation.
mmozeiko
1) It looks like the glyphs are created with smaller size and then rendered with bigger size, upscaled. Or is because whole handamde hero back buffer is 2x upscaled at the end?

2) probably wrong size/position calculations somewhere. Its easy to make mistake when, for example, rectangle is returned with left/right coordinates inclusive, but then you sometimes calculate width=right-left and loose one pixel right on the border. Or other similar calculation.


1. Everything is identical to what Casey does. However his draw buffer is
1
Win32ResizeDIBSection(&GlobalBackbuffer, 1920, 1080);


In my case I do

1
2
3
4
static const uint32_t WindowWidth = 1920;
static const uint32_t WindowHeight = 1080;
static const uint32_t FramebufferWidth = WindowWidth / 2;
static const uint32_t FramebufferHeight = WindowHeight / 2;


Because of the retina display. I do not upscale the back buffer manually. The fonts are rendered the same way Casey does.

I can actually downscale them:
1
2
3
r32 Scale = stbtt_ScaleForPixelHeight(&Font, 64.0f); // instead of 128.0f
...
AtY -= 1.2f * 40.0f * FontScale; // instead of 80.0f


Then it looks like this (w/out alpha blend): https://gyazo.com/052f4a68116f7a9cbd60882f3bbe2a29 or https://gyazo.com/c6313aa0def7bc35fb7dd5fe64c4f4f8 (with alpha blend)
Closer to what Casey shows in videos. But you can still see the quality is off somehow.

2. Yes, but for the love of me I still can't figure it out. It's driving me mad :-) I feel like I'm loosing pixels on floating point to int conversion.
Oh, I misread. I didn't realize you are using stb for ttf fonts. I though we are still talking about OSX font rendering.

How HH code is doing rendering? Software or OpenGL? If I'm not mistaken assets require 1 pixel padding around bitmap which will be discarded due to more efficient code in software rendering. So if there are some relevant pixels right on bitmap border, they will be clipped. Have you done any modifications to rendering code, or is it original handmade hero code?

Edited by Mārtiņš Možeiko on
mmozeiko
Oh, I misread. I didn't realize you are using stb for ttf fonts. I though we are still talking about OSX font rendering.

How HH code is doing rendering? Software or OpenGL? If I'm not mistaken assets require 1 pixel padding around bitmap which will be discarded due to more efficient code in software rendering. So if there are some relevant pixels right on bitmap border, they will be clipped. Have you done any modifications to rendering code, or is it original handmade hero code?


The code is original Casey's, at this stage rendering is software. On platform level I use OpenGL to draw the buffer passed from DLL function onto the screen, but nothing else.
After some reading I'm inclined to think that I won't get a better font image quality due to algorithms used in STB. I am almost ready to give up on that particular subject. But the clipping still annoys me.
hey @r2d2
I was also experiencing font clipping when I was rasterising ttf font(to texture-atlas) with FreeType library. It took me 4 days to find the fix.
From my understanding the clipping occur because of incorrect mapping of texels to pixel. Here is more about it. For more on this issue search for half pixel correction.

What you need to do is, offset the texture coordinates by half pixel. For example

1
2
NewTexCoord.u = TexCoord.u - (0.5/Width);
NewTexCoord.v = TexCoord.v - (0.5/Height);


Hoping it fixes the clipping.

Edited by skye_r on
For the font rendering quality, Sean Barrett (the creator of the stb libs) tweeted some tests he made for better text quality. I can't find it back (you can probably ask him for the link) but if I remember correctly the conclusion was that you need to render glyphs at a higher resolution that the glyphs display resolution and not do srgb convertion.

For the clipping you can try to display the character bitmaps (like trees, without alignment or anything) to see if you baked them correctly or output them as bmp/tga files to see if they look correct in an image editor.

Have you tried using stbtt_GetCodepointBitmapBox instead of stbtt_GetCodepointBox (and maybe stbtt_MakeCodepointBitmap instead of stbtt_GetCodepointBitmap) ? You may need to convert the coordinates (bottom up to top down). stb_truetype.h contains examples at the beginning of the file.
2 skye_r:

Thanks, I was thinking in the same direction but was looking into ability of STB to do subpixel rendering. Haven't had the chance yet to read about it. I'll try to apply your advice somehow because it definitely feels like a fraction of the pixel. If I expand the width or height of the bitmap even by one pixel I get artefacts in a form of next glyph's contours showing through on the right or on the bottom.

2 mrmixer:

Thanks for advice. I was just looking into the idea of writing out the image into file to see how it looks outside of the rendering engine.

was that you need to render glyphs at a higher resolution that the glyphs display resolution and not do srgb convertion.
What is higher resolution in this case? Right now it's baked into the asset file, the font is scaled to a height of 128.0f pixels (64.0f in my case) and rendered as is inside the actual game. FontScale is set to 1.0f and it is multiplied by width or height of the actual bitmap which is taken out of stb's functions when packing the asset file (see the gist snippet above).

EDIT: Actually, this is a great idea. I scale the glyph to the original size from the video (i.e. to 128 pixels), but apply 0.5 scale instead, looks much nicer and no artefacts! https://gyazo.com/ccb2a2093f87a5d75399fb0328147ddb Now I only have to figure out the clipping :)

Regarding stbtt_GetCodepointBitmapBox and stbtt_GetCodepointBox, I don't get what is the difference, to be honest.

Here's debug info for stbtt_GetCodepointBox
1
2
3
4
X0: 1
X1: 30
Y0: -10
Y1: 24


and for stbtt_GetCodepointBitmapBox
1
2
3
4
cX0: 1
cX1: 31
cY0: -25
cY1: 11


Why the Y's are inverted and why the numbers differ slightly?

I've tried using stbtt_MakeCodepointBitmap but haven't figured out how to do it properly. The best I got is that for each glyph it was rendered four times into bitmap, probably because stb returns an 8 bit bitmap and the resulting bitmap is 4 bytes per pixel. But I didn't spend much time with it yet.

Edited by r2d2 on
I've played around with half-pixel offsets. It has fixed the clipping issue, thank you, skye_r!

Did some tests for the image quality as well. That's how it looks written out into bmp right after extraction from the TTF file: https://gyazo.com/4fc0d0c2516335bb6aca6f6e10408d3a

And this is how the same letter looks rendered by the engine: https://gyazo.com/e31e7d7b6cab4b5b9e575b90e87ba2ed

For some reason, when I do a 0.5f scale inside the engine for the font bitmaps, everything starts to look ugly. When rendered in original 1.0f scale, it looks nice, but the letters do not fit onto the screen (which is expected, because my draw buffer is halved): https://gyazo.com/f87b9fb36fe7a5f8926fcbe908a18942

But I guess it's the issue of me downscaling the draw buffer. The overall image quality is better if I do not half-scale it: https://gyazo.com/c5529232f0bae5d0fa7693e790205a68 This goes to full-screen, basically, at 1920x1080 and the fonts are equal to the original version.
I found Sean Barrett's test.

For the difference between the functions, you can search for them in stb_truetype.h and you'll get a short description. The differences in value might get you the right position and avoid the half pixel offset. The name suggests to me that it returns values specific to the bitmap representation: if the renderer added a pixel at the bottom for anti-aliasing, the raw value would be off, but GetCodepointBitmapBox would return a corrected value instead of the raw value from the font file (It's a guess, I haven't checked if it's actually what's happening). And the Y is expected to be inverted (see description below).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1);
// Gets the bounding box of the visible part of the glyph, in unscaled coordinates

STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1);
// get the bbox of the bitmap centered around the glyph origin; so the
// bitmap width is ix1-ix0, height is iy1-iy0, and location to place
// the bitmap top left is (leftSideBearing*scale,iy0).
// (Note that the bitmap uses y-increases-down, but the shape uses
// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.)

STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint);
// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap
// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap
// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the
// width and height and positioning info for it first.