Da Vinci Font version 1.0 Technical Notes By Stephen White of ArtisTech Development The following information explains the format of Da Vinci fonts. This information has been included for the person who: 1. would like to develop a program which would use Da Vinci fonts. 2. would like to convert to and from Da Vinci fonts and other existing font formats (our favorite kind of person!). 3. is curious about how such things as fonts are programmed (our second favorite kind of person). If you do not fit into any of the above categories, then don't bother reading this section. It'll bore you to tears, and it is not necessary to know how Da Vinci fonts were programmed to use them. Introduction: When deciding what font format to use with Da Vinci, I had a few requirements: color, kerning, & proportional size. Unfortunately, I was unable to find an existing ATARI ST font format which met all of these requirements. Oh, no! Not another font format!?! Yes, I decided to design a font format that would be powerful for working with screen graphics. Da Vinci's font format is freely distributable and usable, but we at ArtisTech Development ask that you do not modify the existing font format without contacting us first. We are easy- going people, and if you have found a limitation in our font format, then we would like to know about it. Perhaps, together we could develop a new standard which would correct the limitation, but still retain some degree of compatibility. Uncompressed Font Format: Da Vinci fonts are stored to diskette in a compressed format, but I will first explain the font's format in its uncompressed version (exactly as a font is stored within Da Vinci itself). I will use the two characters '0x' to specify that the value following those two characters is a hex value. For example, a 0x20 translates to 32 decimal (not 20 decimal). The first four bytes of the font spells the word 'FONT' (0x464f4e54). These four bytes are used solely for identifying the file as a Da Vinci font. The next 24 bytes holds the descriptive name of the font. The name should be null terminated (a character of value 0 stored after the last character of the name) if it is less than 24 bytes in size. The descriptive name is simply a string, and can contain any printable characters with the only exception being that lowercase letters are not currently supported by Da Vinci. The next byte specifies the number of bit planes of the font. The number of colors in the font is equal to: 2^number of bit planes (two to the power of the number of bit planes). For example, 1 bit plane has 2 colors, 2 bit planes has 4 colors, 3 bit planes has 8 colors, 4 bit planes has 16 colors, etc.. The next byte specifies the starting (lowest ASCII value) character in the font. The font's range of characters is defined by a starting character and an ending character. The starting charater should be the character with the lowest ASCII value in the font. The ending character should be the character with the highest ASCII value in the font. Each Da Vinci font only has one range of characters. The next byte specifies the ending (highest ASCII value) character in the font. See the previous paragraph. The next byte specifies the unknown character. The unknown character is the character to print if the user types a character which is not in the font. If the unknown character is a -1 (0xff), then there isn't a character to print. The unknown character is usually a space (0x20). The next byte specifies the color to use as the transparent color for generating a mask. Each character must share this transparent color. The transparent color is usually color 0. In fact, currently Da Vinci ignores this value and assumes that it is a 0, since masking color 0 can be done quickly and on the fly (without generating a mask in memory). The next byte specifies what color to use to draw the underline. The next byte specifies the bold smear. The bold smear is a count of how many times to redraw a character to create the bold text style. Each redraw/smearing of the character should be done one pixel to the right of the previously drawn character. This value is normally a 1. In fact, currently Da Vinci ignores this value and assumes that it is a 1, since 1 pixel in low resolution is quite large. A smear of more than 1 pixel in low resolution would look terrible! The bold smear value was included in the format so that higher resolutions could use it. The next byte is unused. What a waste? Oh come on! Everyone does it. This byte was added to the format so that the font format could be modified later while still remaining compatible. For now, this byte should have a value of 0. Please do not use this byte unless you have contacted us. This single byte could be used later to specify multiple modification to the font format. By contacting us before using this byte, we can develop a new standard and maintain compatibility. The next word is the ascent line offset. The ascent line offset is a positive offset of how far (in pixels) the ascent line is from the base line. For example, if the ascent line rested 1 pixel above the base line then the ascent line offset would be 1. The ascent line is used to specify the top of the tallest character in the font. The next word is the half line offset. The half line offset is a positive offset of how far (in pixels) the half line is from the base line. For example, if the half line rested 1 pixel above the base line then the half line offset would be 1. The half line is used for kerning purposes to seperate the top of the taller characters from the smaller characters. The half line is usually positioned at the top of a lower case letter such as 'n'. The next word is the descent line offset. The descent line offset is a positive offset of how far the descent line is below the base line. The descent line is used to specify the bottom of the lowest (graphically) character in the font. The descent line is usually positioned at the bottom of the letter 'y'. The next word is the bottom line offset. The bottom line offset is a positive offset of how far the bottom line is below the base line. The bottom line is used to specify where the ascent line of a line of text typed beneath the current line of text should be placed. In other words, when the user presses carriage return, then the new line of text should have its ascent line equal to the previous line's bottom line. That's it for the overall font header. Next comes the individual character's information and graphics. Each character has its own header and graphic data. The first header and graphic data belongs to the starting character (defined earlier). The next header and graphic data belongs to the next character, etc.. The header and graphic data for each character is defined as follows: The first word is an offset in bytes which, when added to the address of where this offset is in memory, will point to the memory location of the next character's header. This makes scanning the font for a character very easy. For example, if the user types a character, simply do the following: 1. Convert the character to ASCII. If the character is not in the range of the font, then handle that appropriately. 2. Get the address of the font in memory. 3. Add 44 to the address of the font in memory so that you have the address of the starting character's header. 4. Subtract the starting character from the typed character (using ASCII values). If the result is 0, then the typed character is the starting character so you've found the character. If not a 0, then continue to step 5. 5. Add the offset at the current memory address to the current memory address. 6. Subtract 1 from the difference between the typed character and the starting character. If the result is 0, then you now have the memory location of the typed character's header. If the result is not 0, then go back to step 5. In 68000 assembly, the above would look like: movea.l font_ptr, A0 ; get the address of the font move.b typed_char, D0 ; get the typed character cmp.b 29(A0), D0 ; is character below starting character? blt notinrange ; yes, so not in font cmp.b 30(A0), D0 ; is character above ending character? bgt notinrange ; yes, so not in font sub.b 29(A0), D0 ; typed character- starting character ext.w D0 ; use words from now on adda.w #44, A0 ; point to starting character's header tst.w D0 ; if 0, character is starting character beq foundheader ; yes, so found header subq.w #1, D0 ; -1 for looping loop: adda.w (A0), A0 ; skip to next character's header dbf D0, loop bra foundheader If the offset is 0, then there aren't any more character headers, and the palette data directly follows. The word (the offset) following the ending character's data MUST BE A 0 so that the palette data can be scanned for very quickly. It should be noted that this system of using a signed offset does have a small drawback. The offset value cannot exceed a value of 32767. This does put a limit on the size of a character's graphics, but not much of one. For example, a two color character can be defined to have a pixel width of 640 and a height of 400. Also, a 16 color character can be defined to have a pixel width of 320 and a height of 200. The second word in the character's header is the pixwidth (width in pixels) of the character. If this value is 0, then the character doesn't have any further header or graphic data, and the unknown character should be used (the unknown character was defined earlier). The third word of the character's header is the height (in pixels) of the graphic data of the character. This value is used so that the graphics of a character need not be the size of the ascent line offset plus the descent line offset plus 1. Instead, the graphic can have any height above a value of 0. The fourth word of the character's header is the base line offset of the character. The base line offset is the signed offset from the base line to where the first line (top) of the character should be drawn. A negative offset means that the top of the character is above the baseline, and a positive offset means that the top of the character is below the baseline. To draw a typed character, I expanded the graphics of the character to a total height equal to the ascent line offset plus the descent line offset plus 1. I positioned the graphics of the character within this expanded character according to the base line offset. I filled the unused areas of the expanded character with the transparent color (defined earlier). Actually, remember that I cheated and assumed that the transparent color was color 0. The number of lines of unused area above the top of the character is equal to the ascent line offset plus the base line offset. The number of lines of unused area below the bottom of the character is equal to the descent line offset minus the base line offset minus the character's height plus 1. Once the character was expanded, then printing was a piece of cake. The graphic data of the character follows the base line offset. Each bit plane is stored seperately, with bit plane 0 being the first bit plane stored, followed by the other bit planes in consecutive order. The graphic data is stored line by line, with the words wide of each line being equal to the pixwidth/16 plus 1 if pixwidth%16 (the remainder) is not equal to 0. The total size of the graphic data is equal to the words wide*height*number of bit planes*2. Here comes the tricky part. If after reading off all of the graphics data, the next memory location is not the address of the next character's offset, then there is kerning data attached to the character. This really isn't hard to test for. Honest! When you find the memory address of the header of the character to print, then add that character's offset to the memory location to find the memory location of the next character's header. Hold on to that memory address. Now, after reading the graphic data of the character to print, you should easily be able to get the memory address of the word that directly follows the graphic data. Compare this address with the address you saved earlier. If they are different, then there is kerning data following the graphic data. If they are the same, then there isn't any kerning data. Why such a wierd way of storing kerning? The advantage of this method is that it doesn't cost any memory to specify whether there is kerning data. This is handy since most fonts will not contain kerning. The kerning data is used to specify how close another character can come to the typed character. Each piece of kerning data is a byte in size and is signed. If the kerning on the left size of the character is negative, it is specifying how far (in pixels) the current character can overlap onto the previously typed character. If the kerning on the left size of the character is positive, it is specifying how far away the current character needs to be from the previously typed character. A kerning value of 0 would have the same effect as if there wasn't any kerning. One special case of kerning on the left is a kerning value of 0x80, which specifies that the current character can overlap completely onto the previously typed character. Kerning on the right side of the character is similar to kerning on the left side of the character. A positive kerning value is how far the next character must be from the current character. A negative kerning value is how far the next character can overlap the current character. A kerning value of 0 has the same effect as if there wasn't any kerning. A special case kerning value of 0x80 means that the current character can be overlapped completely by the next character. Da Vinci supports 6 bytes of kerning data: 3 on the left and 3 on the right. The kerning is divided into 3 areas: top (between the half line and the ascent line), middle (between the half line and the base line), and bottom (between the base line and the descent line). The first byte of kerning data is the top left kerning. The second byte of kerning data is the middle left kerning. The third byte of kerning data is the bottom left kerning. The fourth byte of kerning data is the top right kerning. The fifth byte of kerning data is the middle right kerning. The sixth byte of kerning data is the bottom right kerning. The positioning of characters using kerning should be done as follows: 1. Set up three starting x positions: top_edge, middle_edge, and bottom_edge. Set them all equal to the x position of the text cursor on the screen. 2. If the middle left kerning equals 0x80 then go to step 10. 3. Sum the middle_edge and the middle left kerning value. Store this value as the xposition. Now, the top and bottom of the character can not move closer to the previous character, but make certain that they don't have to move farther away. 4. If the top left kerning equals 0x80 then no problem with top, so go to step 7. 5. Find the difference between the xposition and the top left kerning (xposition- top left kerning). If the difference is greater than or equal to the top_edge, then no problem with top, so go to step 7. 6. Negate the difference. Add the top_edge to the negated difference. Add the sum to the xposition. Both the top and the middle edges are now ok. 7. If the bottom left kerning equals 0x80 then no problem with bottom, so go to step 15. 8. Find the difference between the xposition and the bottom left kerning (xposition- bottom left kerning). If the difference is greater than or equal to the bottom_edge, then no problem with bottom, so go to step 15. 9. Negate the difference. Add the bottom_edge to the negated difference. Add the sum to the xposition. All edges are now ok, so go to step 15. 10. The middle left kerning has no width (0x80), so try top left kerning. If top left kerning has no width (0x80), then go to step 12. 11. Sum the top_edge and the top left kerning value. Store this value as the xposition. Now the top and middle edges are ok, so make certain that the bottom edge is ok. Go to step 7. 12. Both top left and middle left kernings have no width (0x80), so bottom left kerning better have a width. If bottom left kerning does not have a width, then go to step 14. 13. Sum the bottom_edge and the bottom left kerning value. Store this value as the xposition. All edges are now ok, so go to step 15. 14. All left kernings have no width, so store middle_edge as xposition. 15. Xposition now holds the x position on the screen to draw the character. Now it's time to update top_edge, middle_edge, and bottom_edge. 16. The right edge equals xposition plus pixwidth. Add one more to the right edge if the bold font style is being used. 17. If the top right kerning value does not equal 0x80, then the sum of the right edge plus the top right kerning value should be stored as the top_edge. 18. If the middle right kerning value does not equal 0x80, then the sum of the right edge plus the middle right kerning value should be stored as the middle_edge. 19. If the bottom right kerning value does not equal 0x80, then the sum of the right edge plus the bottom right kerning value should be stored as the bottom_edge. 20. Now that wasn't so bad, was it? The position of characters without kerning is easy. Do the following: 1. Find which of the edges (top_edge, middle_edge, and bottom_edge) is the right most (largest). Store the right most edge as the xposition. 2. Calculate the new edges. The new edge equal the xpositon plus the pixwidth. Add one more if the bold font style is being used. Store the new edge as top_edge, middle_edge, and bottom_edge. 3. I told you it was easy. The font's palette data is stored as the last of the font data. To find the palette data, simply skip from character header to character header until a character offset of 0 is found. The palette data directly follows the character offset of 0. The palette data is stored as 3 bytes for every color: the first byte is the amount of red of the color, the second byte is the amount of green of the color, and the third byte is the amount of blue of the color. The color bytes cover a color range of 0 to 255. The ATARI ST has a color range of 0 to 7, so each of the red, green, and blue values should be logically shifted to the left by 5 before being stored as palette data. The size of the palette data is equal to 3*(2^numplanes), or 3 times the number of colors in the font. Quick Reference of Uncompressed Font Format: Font Header: offset name size description ========================================================================== 0 ID 4 Always 0x464f4e54 ('FONT'). 4 name 24 Descriptive font name. 28 numplanes 1 Number of bit planes. 29 startingchar 1 Starting character in font. 30 endingchar 1 Ending character in font. 31 unknownchar 1 Character print if not within font range. 32 transparent 1 Transparent color used for masking. 33 underline 1 Color of underline. 34 boldsmear 1 Amount of times to smear for bold. 35 unused 1 Currently always 0. 36 ascentoffset 2 Positive offset of ascent line from base. 38 halfoffset 2 Positive offset of half line from base. 40 descentoffset 2 Positive offset of descent line from base. 42 bottomoffset 2 Positive offset of bottom line from base. 44 character data & palette Character data: offset name size description ========================================================================== 0 offsetnext 2 Positive offset to next character. If 0, then palette data is next. 2 pixwidth 2 Pixel width of character. If 0, then no more header or graphic data. 4 height 2 Height of character 6 baselineoffset 2 Offset of top of character from base. 8 graphicdata Kerning data: offset name size description ========================================================================== 0 top_left 1 Top left kerning value. 1 middle_left 1 Middle left kerning value. 2 bottom_left 1 Bottom left kerning value. 3 top_right 1 Top right kerning value. 4 middle_right 1 Middle right kerning value. 5 bottom_right 1 Bottom right kerning value. Palette data: The palette data is stored as 3 bytes for every color: the first byte is the amount of red, the second is the amount of green, and the third is the amount of blue. The values are stored as 0- 255 so ST values must by shifted by 5. The size of the palette data is equal to 3*(2^numplanes).