Osdev-Notes

Drawing Fonts

When framebuffer is enabled, the hardware bios video memory is no longer accessible, and the only things that we can do now is drawing pixels.

So in order to write text we need first to have at least one font available to our operating system and how to store it.

The font can be one of many different available (ttf, psf, etc.) in this tutorial we will use Pc Screen Font v2 aka PSF (keep in mind that v1 has some differences in the header, if we want to support that as well, the code needs to be adapted)

How the font is stored depends on the status of the operating system, there are several way:

If running on linux there are some nearly ready to use fonts in /usr/share/kbd/consolefonts (path can change slightly depending on the linux distribution)

In this chapter we are going to see how to use a font that has been stored into memory.

The steps involved are:

  1. Find a suitable PSF font
  2. Add it to the kernel
  3. When the kernel is loading identify the PSF version and parse it accordingly
  4. Write functions to: pick the glyph (a single character) and draw it on the screen

Embedding a PSF File In The Kernel

As already said the best place to look for a font if running on linux is to look into the folder /usr/share/kbd/consolefonts, to know the psf version the tool gbdfed can be used, just import the font with it (use the File->Import->console font menu), and then go to View->Messages, there should be a message similar to the following:

Font converted from PSF1 to BDF.

Once we got the font, it needs first to be converted into an ELF binary file that can be linked to our kernel.

That can be done using the command objcopy (on linux, if on a different operating system search for a suitable alternative):

objcopy -O elf64-x86-64 -B i386 -I binary font.psf font.o

The objcopy command is a tool that copy a source file into another, and can change its format. The parameters used in the example above are:

Once converted into binary elf, it can be linked to the kernel like any other compiled file, in this case we just need to add the output file to the linker command:

ld -n -o build/kernel.bin -T src/linker.ld <other_kernel_files> font.o -Map build/kernel.map

With the font linked, now is possible to access to 3 new variables, like in the following example:

readelf -s font.o

Symbol table '.symtab' contains 5 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     2: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT    1 _binary_font_psf_start
     3: 0000000000008020     0 NOTYPE  GLOBAL DEFAULT    1 _binary_font_psf_end
     4: 0000000000008020     0 NOTYPE  GLOBAL DEFAULT  ABS _binary_font_psf_size

(the variable name depends on the font file name).

Parsing the PSF

Since there are two different version of PSF fonts available, identified with v1, and v2, to know what is the version of the font loaded we need to check the magic number first:

#define PSF1_MAGIC0     0x36
#define PSF1_MAGIC1     0x04
#define PSF2_MAGIC0     0x72
#define PSF2_MAGIC1     0xb5
#define PSF2_MAGIC2     0x4a
#define PSF2_MAGIC3     0x86

The magic number is stored from the least significant byte (0) to the more significant (2 or 4 depending on the version)

PSF v1 Structure

For version 1 of the psf, the data structure is pretty simple and contains only three fields:

All the fields above are declared as unsigned char variables, except for the magic number that is an array of 2 unsigned char. For version 1 fonts there are few values that are always the same:

The font data starts right after the header.

PSF v2 Structure

The psf structure header has a fixed size of 32 bytes, with the following information:

All the fields are 4 bytes in size, so creating a structure that can hold it is pretty trivial, except for the magic number that is an array of 4 usnigned char.

Let’s assume from now on that we have a data structure called PSF_font with all the fields specified above. The first thing that we need of course, is to access to this variable:

// We have linked _binary_font_psf_start from another .o file so we must
// specify that we are dealing with an external variable.
extern char _binary_font_psf_start;
PSF_font *default_font = (PSF_font *)&_binary_font_psf_start

Glyph

Now that we have access to our PSF font, we can work with “Glyphs”. Every character (Glyph) is stored in a bitmap. Each bitmap is WIDTH x HEIGHT pixel . If for example the glyph is 8x16, it will be 16 bytes long, every byte encode a row of the glyph. Below an example of how a glyph is stored:

00000000b  byte  0
00000000b  byte  1
00000000b  byte  2
00010000b  byte  3
00111000b  byte  4
01101100b  byte  5
11000110b  byte  6
11000110b  byte  7
11111110b  byte  8
11000110b  byte  9
11000110b  byte 10
11000110b  byte 11
11000110b  byte 12
00000000b  byte 13
00000000b  byte 14
00000000b  byte 15

(This is the letter A).

The glyphs start right after the psf header, the address of the first character will be then:

uint8_t* first_glyph = (uint8_t*) &_binary_font_psf_start +
    default_font->headersize

Since we know that every glyph has the same size, and this is available in the PSF_Header, if we want to access the i-th character, we just need to do the following:

uint8_t* selected_glyph_v1 = (uint8_t*) &_binary_font_psf_start +
    sizeof(PSFv1_Header_Struct) + (i * default_font->bytesperglyph);

uint8_t* selected_glyph_v2 = (uint8_t*) &_binary_font_psf_start +
    default_font->headersize + (i * default_font->bytesperglyph);

Where in the v1 case, PSFv1_Header_Struct is just the name of the struct containing the PSFv1 definition.

If we want to write a function to display a character on the framebuffer, what parameters it should expect?

Before proceeding let’s talk about the position parameters. Now what they are depends also if we are implementing a gui or not, but let’s assume that for now we want only to print text on the screen, in this case X and Y do not represent the pixel coordinates, but characters for example x=0, y=1 it goes to the column 0 (x) and to the row y is down 1 * font->height pixel

So our function header will be something like that:

void fb_putchar( char symbol, uint16_t x, uint16_t y, uint32_t fg, uint32_t bg)

Clearly what it should do is read the glyph stored in the position given by symbol, and draw it at row x and column y (don’t forget they are “character” coordinates) using colors fg for foreground color and bg for background (we draw the foreground color when the bit in the bitmap is 1, and the bg color when is 0).

We already saw above how to get the selected glyph, but now how we compute the position in the screen? In this case we need first to know:

The number of bytes in each line, assuming that we are using grub and the framebuffer is configured via the multiboot header, is available in the multiboot_tag_framebuffer structure, the field is framebuffer_pitch.

Implementing the function above should be pretty simple and is left as exercise.