VGA Fonts: Difference between revisions

3,894 bytes added ,  29 days ago
m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
No edit summary
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(20 intermediate revisions by 12 users not shown)
Line 1:
{{TutorialTone}}
So you know how to display characters in text mode, and now you want to do it in graphics mode. It's not complicated, but definitely more complex than writing and ASCII code at a specific offset in memory. You'll have to do it pixel by pixel.
So you know how to display characters in text mode, and now you want to do it in graphics mode. It's not complicated, but definitely more complex than writing an ASCII code at a specific offset in memory. You'll have to do it pixel by pixel.
 
But how do you know what to draw? It's stored in data matrix called bitmap fonts.
Line 5 ⟶ 6:
== Decoding of bitmap fonts ==
How is a character stored in memory? It's quite simple, 0 encodes background, 1 encodes foreground color. VGA fonts are always 8 bits wide so that each byte contains exactly one row.
For letter 'A' in the typical 16x88x16 font it would be (in binary):
<pre>
00000000b byte 0
Line 25 ⟶ 26:
</pre>
The full bitmap contains bitmaps for every character, thus it's 256*16 bytes, 4096 bytes long. If you want to get the bitmap for a specific character, you have to multiply the ASCII code by 16 (number of rows in a character), add the offset of your bitmap and you're ready to go.
 
A very simple file format to store these is [[PC Screen Font]], used by the Linux Console. It stores fonts the way described above with a small header. Another solution is the [[Scalable Screen Font]] format which comes with an extremely small, free rendering ANSI C library.
 
== How to get fonts? ==
There're several ways. You can have it in a file on your filesystem. You can hardcode it in an array. But sometimes 4k is so much that you cannot afford, and reading a file is not an option (like in a boot loader), in which case you'll have to read the one used by the card (to display text mode characters) from VGA RAM.
Line 30 ⟶ 34:
Easiest way, but increases your code by 4k. There are several sources that provide the entire font in binary or source format so you do not need to manually write it out.
=== Store it in a file ===
Most modular way. You can use different fonts if you like. Downside you'll need a working filesystem implementation. As for the file format, I'd suggest the aforementioned [[PC Screen Font]] (.psf/.psfu) or [[Scalable Screen Font]] (.sfn).
 
=== Get the copy stored in the VGA BIOS ===
It's a standard BIOS call (no need to check it's persistence). If you're still in real mode, it's quite easy to use.
<sourcesyntaxhighlight lang="asm">
;in: es:di=4k buffer
;out: buffer filled with font
Line 50 ⟶ 55:
rep movsd
pop ds
</syntaxhighlight>
</source>
 
=== Get from VGA RAM directly ===
Maybe you're already in protected mode, so cannot access BIOS functions. In this case you can still get the bitmap by programming VGA registers. Be careful that the VGA always reserves space for 8x32 fonts so you will need to either copy 8k of data or trim off the bottom 16 bytes of each character during the copy:
<sourcesyntaxhighlight lang="asm">
;in: edi=8k4k buffer
;out: buffer filled with font
;clear even/odd mode
Line 68 ⟶ 74:
out dx, ax
;clear even/odd mode (the other way, don't ask why)
mov ax, 0704h0604h
out dx, ax
;copy charmap
mov esi, 0A0000h
mov cxecx, 256*32/4
;copy 16 bytes to bitmap
rep movsd
@@: movsd
movsd
movsd
movsd
;skip another 16 bytes
add esi, 16
loop @b
;restore VGA state to normal operation
mov ax, 0302h
out dx, ax
mov ax, 0304h0204h
out dx, ax
mov dx, 03ceh
mov ax, 1005h
out dx, ax
mov ax, 0A06h0E06h
out dx, ax
</syntaxhighlight>
</source>
It worth mentioning that it has to be done '''before''' you switch to aVBE graphics mode, because the VGA fontregisters isare wipedusually fromnot memoryaccessible duringafterwards. transitionThis andmeans especiallyyou inwon't thebe caseable ofto VBEmap wherethe VGA registerscard's arefont usuallymemory notto accessiblescreen afterwardsmemory, and you will read only garbage.
 
== Set VGA fonts ==
If you're still in text mode and want the VGA card to draw different glyphs, or you want to change back from a graphics mode, you can set the VGA font. It's worthless in graphics mode (because characters are displayed by your code there, not by the card), I only wrote this section for completeness. Modifying the font bitmaps in VGA RAM isn't hard if you read carefully what's written so far. I'll left it to you as a homework.
 
=== Set fonts via BIOS ===
Line 94 ⟶ 107:
 
=== Set fonts directly ===
Hint: use the same code as above, but swap source and destination for "rep movsd".
 
== Displaying a character ==
And finally we came to the point where we can display a character. I'll assume you have a putpixel procedure ready.
Okay, we have a font bitmap, but how to use it? Here's a simple example:
We have to draw 8x16 pixels, one for every bit in the bitmap.
<source lang="c">
<syntaxhighlight lang="c">
//this is the bitmap font you've loaded
unsigned char *font;
Line 106 ⟶ 120:
int cx,cy;
int mask[8]={1,2,4,8,16,32,64,128};
unsigned char *gylphglyph=font+(int)c*8*16;
 
for(cy=0;cy<16;cy++){
for(cx=0;cx<8;cx++){
putpixel(glyph[cy]&mask[cx]?fgcolor:bgcolor,x+cx,y+cy-12);
}
}
}
</syntaxhighlight>
</source>
The arguments are straightforward. You may wonder why to subtract 12 from y. It's for the baseline: you specify y coordinate as the bottom of the character, not counting the "piggy tail" in a glyph that goes down (like in "p","g","q" etc.). I other words it's the most bottom row of letter "A" that has a bit set.
It's not so complicated. You supply the function with arguments:
 
<pre>
Although it's mostly useful to erase the screen under the glyph, in some cases it could be bad (eg.: writing on a shiny gradiented button). So here's a slightly modificated version, that uses a transparent background.
c - the character's ASCII code you want to display
<syntaxhighlight lang="c">
x,y - where to draw
fgcolor - foreground color
bgcolor - background color
</pre>
It could be handly to draw the background as well, because it erase the area under the glyph. But sometimes it could be annoying, so here's a slightly modificated version that's using transparent background:
<source lang="c">
//this is the bitmap font you've loaded
unsigned char *font;
Line 131 ⟶ 140:
int cx,cy;
int mask[8]={1,2,4,8,16,32,64,128};
unsigned char *gylphglyph=font+(int)c*8*16;
 
for(cy=0;cy<16;cy++){
for(cx=0;cx<8;cx++){
if(glyph[cy]&mask[cx]) putpixel(fgcolor,x+cx,y+cy-12);
}
}
}
</syntaxhighlight>
</source>
As you can see, we have only haveforeground one fgcolorcolor this time., Theand the putpixel call has a condition,: only invoked if the according bit in the bitmap is set. If it's clear, nothing will happen, thus makes the background transparent.
 
Of course the code above will be excruciatingly slow (mostly due to doing one pixel at a time, and repeatedly recalculating the address for each pixel within the "putpixel()" function). For much better performance, the code above can be optimised to use boolean operations and a "mask lookup table" instead. For example (for an 8-bpp mode):
 
<syntaxhighlight lang="c">
//this is the bitmap font you've loaded
unsigned char *font;
 
void drawchar_8BPP(unsigned char c, int x, int y, int fgcolor, int bgcolor)
{
void *dest;
uint32_t *dest32;
unsigned char *src;
int row;
uint32_t fgcolor32;
uint32_t bgcolor32;
 
fgcolor32 = fgcolor | (fgcolor << 8) | (fgcolor << 16) | (fgcolor << 24);
bgcolor32 = bgcolor | (bgcolor << 8) | (bgcolor << 16) | (bgcolor << 24);
src = font + c * 16;
dest = videoBuffer + y * bytes_per_line + x;
for(row = 0; row < 16; row++) {
if(*src != 0) {
mask_low = mask_table[*src][0];
mask_high = mask_table[*src][1];
dest32 = dest;
dest32[0] = (bgcolor32 & ~mask_low) | (fgcolor32 & mask_low);
dest32[1] = (bgcolor32 & ~mask_high) | (fgcolor32 & mask_high);
}
src++;
dest += bytes_per_line;
}
}
 
 
void drawchar_transparent_8BPP(unsigned char c, int x, int y, int fgcolor)
{
void *dest;
uint32_t *dest32;
unsigned char *src;
int row;
uint32_t fgcolor32;
 
fgcolor32 = fgcolor | (fgcolor << 8) | (fgcolor << 16) | (fgcolor << 24);
src = font + c * 16;
dest = videoBuffer + y * bytes_per_line + x;
for(row = 0; row < 16; row++) {
if(*src != 0) {
mask_low = mask_table[*src][0];
mask_high = mask_table[*src][1];
dest32 = dest;
dest32[0] = (dest[0] & ~mask_low) | (fgcolor32 & mask_low);
dest32[1] = (dest[1] & ~mask_high) | (fgcolor32 & mask_high);
}
src++;
dest += bytes_per_line;
}
}
</syntaxhighlight>
 
In this case the address in display memory is only calculated once (rather than up to 128 times) and 8 pixels are done in parallel (which removes the inner loop completely).
 
The main downside for this approach is that you need a different function for each "bits per pixel", except that 15-bpp and 16-bpp can use the same code. For worst case (32-bpp) the lookup table costs 8 KiB. The lookup table for 32-bpp can be re-used for 24-bpp, and for 4-bpp no lookup table is needed at all. To support all standard bit depths that VBE is capable of; this gives a total of 5 versions of each "draw character" function (4-bpp, 8-bpp, 15-bpp and 16-bpp, 24-bpp, 32-bpp) and 3 lookup tables (8-bpp, 15-bpp and 16-bpp, 24-bpp and 32-bpp) which cost a combined total of 14 KiB of data if you use static tables (rather than dynamically generating the desired lookup table if/when needed).
 
== See Also ==
* [[VGA Hardware]] - if you want to do it on your own
* [[PC Screen Font]] - wiki has a tutorial on how to display them
* [[Scalable Screen Font]] - comes with a small, free ANSI C rendering library
* [[TrueType Fonts]] - a very complex vector font format, partially proprietary
 
== External Links ==
* [http://www.inp.nsk.su./~bolkhov/files/fonts/univga/ UNI-VGA] - A free Unicode VGA font (.bdf)
* [http://sourceforge.net/projects/bdf2c/ bdf2c] - .bdf font to C source converter.
 
[[Category:VGA]] [[Category:Video]]
[[Category:Video]]
[[Category:Fonts]]