FontEdit: The FYSOS Font Creator
Update: 16 Apr 2022: New version allows opening/saving in four formats. My FNT format described below, Linux's PSFv1 and PSFv2 formats, and the Grub PFF2 format. See the 'Other Functions' section below for more information.
Update: 30 Jan 2022: New version allows opening/saving in three formats. My FNT format described below, and Linux's PSFv1 and PSFv2 formats.
Update: 29 Jan 2022: New version now allowing for 32-bit Unicode values instead of only 8-bit ASCII values.
(See the bottom of this page for instructions on converting your existing 'FONT' files to this new 'Font' file.)
Reasoning:
If you are like me, you have a hobby of creating your own operating system.
Back in the day, you could simply send a 16-bit attribute/character pair to the screen hardware and it would display the character for you.
This was due to the video hardware (and firmware) included in the machine, having the necessary function to take this 16-bit value and convert it to a bitmap of the character, pixel by pixel.
This is now called the Legacy firmware and hardware.
However, lately, more and more systems are starting to use a new firmware called UEFI and no longer includes the legacy hardware function to convert that 16-bit value to a bitmap. Therefore, it is up to you, the developer, to display your own font to the screen.
The easiest way is to have an already created, hardcoded if you will, bitmap in memory and simply display a pixel if the bit is set, or bring through the background if the bit is clear.
The difficult and tedious task here is creating that bitmap.
This is why I created this utility, to simply allow me to create a font, saved as some meta-data and a stream of bits. Here is an example:
The app allows you to click on a box to "set" the bit or "clear" the bit saving each character of the font.
To Create a new Font
Click on File, New
- A font can have a fixed width or variable width characters, but must have a fixed height, predetermined at creation time. If you choose a fixed width, all characters will use the same number of pixels in the horizontal direction. If you leave the 'fixed' checkbox clear, they will use an average size, each character adjustable in width. This simply displays this many horizontal pixels per character, but you can modify the width at each character.
- The height is a fixed height, again predetermined. Every character will have this same height in pixels.
- The starting character number is usually 0 if you follow the ASCII notation, however it can be any valid Unicode number from 0x000000 to 0x10FFFF. This is usually the character offset. For example, if you only want to support 'A' through 'Z', use a value of 65 for this starting number. Please note that this is not a requirement. It is up to your Font Engine to do what it wants to with this entry. However, this makes it easy if you pass an 24-bit value as the character offset to your Engine.
- Ending number should be the last Unicode value you include. For example, if you follow the ASCII notation, this can be 127 or 255. This value gives this app a count so that it can create the correct number of characters. For example, a starting value of 65 (Unicode 0x000041) with an ending value of 90 (Unicode 0x00005A) is the English letters 'A' through 'Z', giving a count of 26 characters.
- You can give it a name. This has nothing to do with the file name. This is simply for your Engine's sake. For example, if the file name is 'frnkgoth.fnt', the user may not know exactly what font it is. However, when asked to list all the available fonts, your Engine can display a name: 'Franklin Gothic'. Please note that only 32 bytes of UTF-8 characters are allowed and must include the null termination.
Creating a Character
- Now you can start to create each character's bitmap. Click on one of the white boxes to turn it black. A white box means clear, a black box means set.
- Once you have created this character, use the "Next" button to move to the next.
- Looking back to the image above, using the buttons on the left, you can manipulate the bitmap for this character, or move to the previous or next character.
- The 'U', 'L', 'R', and 'D' buttons will move the pixels in that direction. However, note that if you move a pixel off of the board, it is gone. Moving back in the other direction will leave the pixel clear.
- In non-fixed-width fonts, the 'Minus' and 'Plus' buttons will allow you to change the width of the character. This only changes the width of the current character. Again, if you shrink the width, losing pixels on the right side, they are lost.
- The 'Invert' button does just that. It inverts the pixels. Makes all clear pixels set, and all set pixels clear.
- The 'Restore' button will restore a character back to what it was before you modified this character. However, please note that the restoration point is the character's bitmap at the time when you moved to this character. For example, if you modify the character, then press 'next' to go to the next character, pressing 'prev' to come back to this character saves the restore state. i.e.: The current bitmap, before you make a modification, is what will be restored if you hit the 'restore' button.
- The two 'Flip' buttons simply flip in the respected direction.
- The three Delta controls gives your Engine the ability to move the character up and down or give it an extra width value. These are not used here in the font editor, but can be used in your Font Engine. For example, if you create a subscript character, you can put a positive value in the Vertical Delta field. Then your Font Engine can move down the screen this many pixels to display this character.
Again, the three Delta fields have nothing to do with the font file. The character is independent of these fields. These fields are used by your Font Engine.
See the format of the font file below for more information.
Other Functions
- You can save the font at any time using the 'Save as' and 'Save' menu items.
- You can open an existing font using the 'Open' menu item. If the current font is not saved, it will prompt you to save it.
- You can 'copy' a character, then 'paste' it to another character. Note that if the copied character is wider than the current character, the right side will be lost. Add pixels in the horizontal direction (Plus button) to hold the newly copied character before you paste it.
- You can 'go to' a character using an offset.
- The Mark as non-fixed menu item will simply mark the current open font as non-fixed, if it isn't already. You can then modify the width of individual characters. All PSF fonts are initially fixed when opened. If you mark as non-fixed and modify the width of one or more characters, when you save it as a PSF font, all characters will be expanded to the width of the widest character. If the font was opened as a PSFv1 font, if the new width is no longer 8, you will be asked to save as a PSFv2 font. If you do not, all characters will be expanded/truncated to 8 pixels wide.
- You can change the name of a font using the 'Name...' menu item.
- You can dump the contents of the font to a file. This will dump the bytes to a file in ascii format ready for a "C/C++" source code file. Here is a dump of the System128.fnt font.
- You can load a Linux PSFv1 or PSFv2 Font file and save it back as either format, preserving the Unicode table. In other words, you can open any of the three formats, and then save as any of the three formats. The format you save it as does not have to be the format you opened it as. However, saving as a FNT format will lose the Unicode table if it was found on the opened PSFv1 or PSFv2 format.
- Load any of the four formats via the filename, the filename's extension does not indicate which format it is. The FileOpen() routine checks for the signatures to indicate which format it is.
- To save in a specified format, the filename's extension does indicate what format to save as.
- '.fnt' is the format described on this page.
- '.psf' is the Linux PSF version 1 format. Note that if the width is not 8 bits, anything wider will be lost. If the count of chars is not 256 or 512, blank chars will be added.
- '.psfu' is the Linux PSF version 2 format.
- You can also load a Grub PFF2 font file. However, at the time of this writing, you can only save it as one of the other three mentioned formats. Saving back as a Grub PFF2 font file is not yet implemented.
- Please note that this function, loading and saving as other formats, is currently experimental.
The All-Important File Format:
The file format is explained below. This is the format of the file you need to read from disk, or hard-code some or all of it into your kernel file.
The font file has three sections.
1) a 'struct FONT' header
2) a count of 'struct FONT_INFO' blocks, one for each character in the file.
3) the bit stream. This bit stream is a set of bits for each character.
The Font Header.
#define MAX_NAME_LEN 32
struct FONT {
bit8u sig[4]; // 'Font'
bit8u height; // height of char set
bit8u max_width; // width of widest char in set
bit16u info_start; // zero based offset to the first FONT_INFO block
bit32s start; // starting value (first entry in font == this value) (*** Signed ***)
bit32s count; // count of chars in set ( 0 < count <= 0x10FFFF ) (*** Signed ***)
bit32u datalen; // len of the data section in bytes
bit32u total_size; // total size of this file in bytes
bit32u flags; // bit 0 = fixed width font, remaining bits are reserved
bit8u name[MAX_NAME_LEN]; // utf-8 null terminated
bit8u resv[36]; // reserved and preserved
};
The members are byte aligned, using no padding.
- The sig[4] member was 'FONT' in the old version of this font file. I have now made it 'Font' so that a font engine won't mistakenly think an older file is valid.
- The height field is the height of this font in pixels. Every character in the font has this many pixels in the vertical direction.
- The max_width field is the width of the widest character in the font file.
- The info_start field points to the start of the next section. This is normally a value of 96, the size of this header. However, this field is included so that a later version of this font file can have a new section between this Header and the FONT_INFO section, yet current source code can still use this font file with no modification.
- The start field is the starting character code of the first character in the font file. For example, if you start with the English character of 'A', this value would be 65 (0x000041).
- The count field is the count of characters in the font file. For example, if you start with the English character of 'A' and end with 'Z', this value would be 26.
- The datalen field is the count of bytes in the bitstream section.
- The total_size field is the count of bytes in this file.
- The flags field is 32 one-bit flags. Currently only bit 0 is used and indicates that this font has a fixed width. i.e.: The width must not be changed. Any remaining bits must be preserved if written to.
- The name field is a 32-byte UTF-8 NULL terminated string of the name of this font. This is used within your engine to find or display a readable name.
- The resv field is to be written as zeros when creating a font file, and preserved when updating an existing file.
Notes:- The count limit of 0x10FFFF is the inherent limit due to limit of the Unicode specification. The included source to this App has a smaller limit (64k) simply to save memory and make it easier to code.
- The max_width and height fields have an inherent limit of 255 each. However, this App has a limit of 24 for each, simply to keep the code sane.
- The start and count fields are signed 32-bit members. This is for ease of use in your code. However, with 0x10FFFF max characters allowed in the Unicode system, these fields should never have bit 31 set, which would indicate a negative number on an Intel-based system.)
The structure above is the first 96 bytes of the file. Here is a dump of an example file:
00000000 46 6F 6E 74 0A 08 60 00-20 00 00 00 5F 00 00 00 Font..`....._...
00000010 B6 03 00 00 8A 08 00 00-01 00 00 00 43 6F 75 72 ............Cour
00000020 69 65 72 20 4E 65 77 00-00 00 00 00 00 00 00 00 ier.New.........
00000030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
00000000 46 6F 6E 74 // sig = 'Font'
0A // height = 10 bits
08 // width of widest char = 8 bits
60 00 // zero based offset to the first FONT_INFO block
20 00 00 00 // starting ascii char = 32 = space character
5F 00 00 00 // count of chars in set = 95
B6 03 00 00 // bytes in data section (950 bytes)
8A 08 00 00 // bytes in whole file (2,186 bytes)
01 00 00 00 // flags: bit 0 set = fixed width font
43 6F 75 72 69 65 72 20 4E 65 77 00 00 00 00 00 // Name: "Courier New" + ASCIIZ
00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00
00 00 00 00
All reserved fields should be zero when creating a new file and preserved when updating an existing file.
Following that 96 bytes is a count of 12-byte structures, one for each character in the set:
Note that the info_start field in the header indicates where this block set starts, though is usually 96.
Also note that at this time, if this value is larger than 96, any data between the header and this block set is undefined. This optional space is available for future use.
struct FONT_INFO {
bit32u index; // index of this character in data section
bit8u width; // Width of character
char deltax; // +/- offset to print char
char deltay; // +/- offset to print char (allows for drop chars, etc)
char deltaw; // +/- offset to combine with width above when moving to the next char
bit8u resv[4]; // reserved
};
- The index field is the byte offset within the data section of the first byte containing the bitmap for this character.
- The width field is the width of the character in bits. (The height is found in the first 96 bytes of the file).
- The three delta fields are used to display a character at a given delta from the current position. Used only by your Engine. Has no affect on the characters in the file.
For example, if the deltay value is +1, the engine should display the character one pixel down the screen. Used for characters such as a 'p' character.
The deltaw member overrides the width of the character and allows you to display the next character closer or further apart.
Here is a dump of the first 2 character entries: (no bitmaps. the bitmap follows this set of 12-byte entries)
00000060 00 00 00 00 08 00 00 00-00 00 00 00
0A 00 00 00 08 00 00 00 00 00 00 00
00000060 00 00 00 00 // offset is zero. First character in the data stream
08 // 8 bits wide
00 // signed delta 'x'
00 // signed delta 'y'
00 // signed delta 'w'
00 00 00 00 // reserved
0000006C 0A 00 00 00 // offset is 10. Ten bytes from beginning of data stream
08 // 8 bits wide
00 // signed delta 'x'
00 // signed delta 'y'
00 // signed delta 'w'
00 00 00 00 // reserved
The bitmap data stream follows the blocks above.
It is a bit stream of each character.
The first bit is the upper left pixel of the character, the next bit being the next in the row and so on. (The first bit is bit 7, the next is bit 6, etc.)
The bit stream is NOT right-side padded. i.e.: If the character is 5 bits in width, the first byte of the bit stream holds all of the first row and 3 bits of the second row.
The bit stream IS end padded. i.e.: The next character's bit stream will start on a byte boundary.
For example:
- On an eight bit width character, first byte of the bit stream, bit 7 is the upper left pixel and bit 0 is the upper right pixel.
- On a nine bit width character, first byte of the bit stream, bit 7 is the upper left pixel and bit 7 of the next byte is the upper right pixel on the first row.
You can dump the pixel data to a file for debugging purposes, and more importantly, to hard code into your kernel. Here is an example of a dump file's semi-colon entry:
/* character 27 */
0x00, 0x24, 0x02, 0x50, 0x00,
/* Bitmap:
...
...
...
.1.
.1.
...
...
.1.
.1.
1..
...
...
*/
Please note the following items:
- This app is for Windows only. I use WinXP but have tested with later versions of Windows as well as 64-bit machines.
- No installation or removal. Simply copy the .exe file to a new directory and run it. Removal: Simply delete the file.
- To use, simply execute and use the "FILE" menu to open or create a new font file.
- Use at your own risk.
- If you use this utility to create a font for your system, please send me the font file. I would love to add it to my collection of fonts as well as the list below.
If you have existing FONT files and wish to update to this new format, I have included the source code to a console app that will make the conversion for you. See the .zip file for 'conv.c' and 'conv.h'.
Contact: fys [at] fysnet [dot] net
Download: https://www.fysnet.net/fontedit/fontedit.zip (229k)
Contents: fontedit.exe (for 32-bit, Windows XP preferred) and fontedit64.exe (for 64-bit Windows, Windows 10 preferred)
Current version (01.54.10) is dated: 2022 Feb 5
Win10 x64 version may require updated DLLs.
Source code is available at my book's source code github page.
Here is a list of available fonts:
See my other pages on OS Development:
- https://www.fysnet.net/osdesign_book_series.htm (My book series)
- https://www.fysnet.net/fysos.htm (My OS Project)
- https://www.fysnet.net/ultimate/index.htm
- https://forum.osdev.org/ (OS Dev Forum)