How to convert any TTF font to Positron Include Font file - step by step

Started by trastikata, Jun 28, 2024, 07:10 PM

Previous topic - Next topic

trastikata

F4.JPG

Hello,

this is step-by-step description how to convert and use any TTF font to bitmap table *.INC file to use in positron with any TFT display.

F1.JPG F2.JPG

F3.JPG F5.JPG

Brief:

Based on this article here Custom Fonts for CircuitPython Displays we use FontForge to modify and convert the *.TTF font file to Adobe *.BDF format.

Then from the Adobe *.BDF format file we extract the font information and convert it to Positron *.INC file using the tool BDF_DATA_CONVERT_1_01.zip that I've created and attached - so if there are any problems, let me know... the Adobe *.BDF format is very well described in Wikipedia Glyph Bitmap Distribution Format and I've written the BDF_DATA_CONVERT and the positron display code entirely based on the Wikipedia information.

Steps:

1. Install Font Forge

2. Open the TTF font file in FontForge

3. Set Font Size - From the element menu, select Bitmap Strikes Available. Set Pixel Sizes ... pixels and points, dpi, point size resolution ... in displays are quite confusing (Google it  :)  ) so as reference 24 gives about the font sizes shown earlier.

....

Follow the steps in the original article, no point for me to repeat them with other words. However I've attached some extra screenshots that will make things clear on how-to.

FontForge_Screenshots.zip

4. After you converted the *.TTF font file to Adobe *.BDF format, open the newly created *.BDF file with BDF_TO_FLASH_DATA_CONVERTER.exe (click on BDF FILE button) and click on Button "CONVERT" ... that's all.

The BDF_TO_FLASH_DATA_CONVERTER.exe will extract the necessary information and create a Positron *.INC file  that can be directly used in positron as font source. All necessary information to print the font is there. The new *.INC file is placed in the original *.BDF file directory and has the same name with *.INC extension. The BDF_TO_FLASH_DATA_CONVERTER.exe will automatically open the new  *.INC file. I've also attached a sample *.INC file for you to see what it look likes: IM_FELL_English_SC-24_INC.zip

OF14.jpg

5. The font printing code will depend on your TFT screen but reading the font is universal for all types of PICs (I'll come back to that later). You just include the file in Positron and because the BDF_TO_FLASH_DATA_CONVERTER.exe uses the same structure for any file it creates, the new font file can be simply included or commented out.

OF15.jpg


6. Print the font on TFT. I am currently writing very extensive graphic TFT library, with lots of options text sizing, transparent printing, sizing, anti-aliasing, different decimal and integer print options, many figures and options for them with various font options, colors and gradient colors etc, touch screen calibration with multiple points, touch-screen use, vector sizing for some elements ... and more and more to come.

All procedures and parameters are well described in comment, the actual code however is not at all  >:(  .. bad habits.

However it is not a fast, nor code and device optimized library. I am using lot's of math and often floating point for high figure precision but because it is all Math based, the code is universal, although slower or too slow for low-end devices or some complex fill and draw operations. Also it is not a screen specific code, meaning there no drivers to run various devices - I am only using one type of TFT screen and plan to test it on two more types ... but it would mean each device requires own drivers.

Currently I am writing the math functions for this and to keep RAM usage low I am reusing the RAM space and have to pay extra attention to what is used where. The entire library when all Procedures are called (most are shown in the demo bellow) for the moment uses about 700 bytes of RAM from which about 400 go for procedure calls, complex shapes (lots of trigonometry). Basic Procedures use very little RAM.

Now I am facing a dilemma and looking for advise ... I am not so good in code optimization but I think much can be optimized around the Math. But because I am recycling RAM space and using same, I call them temp variables, the code is getting hard to read back from someone else (and for that matter from me too). But if I use new Variables for each procedure the RAM usage will grow exponentially. And if I alias the variables too many times, it is hard to follow which RAM address is used where ... so any advise from experienced coders?

And maybe if someone is interested in optimizing some of the code or procedures. It is not a commercial project and it is done in own free time, so no obligations or time limits or any commitment that if something is said, then must be done ... also I have 4 spare TFT development PCBs from the batch I ordered from JLCPCB and I can populate them (one I promised to Les), however screens I don't have so many. Meaning that if someone is interested for the purpose of the library development, I could assemble and give 3 boards for free.

And here some part of the functions included so far (you have seen the touch screen Black board earlier too).








John Drew

An exciting program Trastikata. Later it should go in the Wiki.
Re the maths I have found that a noticeable reduction in file size and increase in speed by breaking complex algorithms into short lines using intermediate variables.
John

trastikata



Tool update:

Now if the font number of characters was trimmed, the tool will scan and write the maximum bounding box and offsets for the remaining characters and will post it by default, the original font bounding box and maximum offsets are commented out.

Also added underscore at the end of the data tables to make it compatible between Positron8 and Positron16.

trastikata

Here's how to use the Font table file, I've attached a file as an example.

Using Font Forge we can export to the .bdf file only selected characters if we don't have to use all of them. However it is preferable and probably much easier if the available characters to export follow each other in the ASCII order, meaning don't skip characters but trim only beginning and end of the font.

In this example file, I want to use (print) only digits, therefore I don't need the rest of ASCII table characters to take FLASH memory space and I trimmed it to only ASCII characters from 48 to 57.

File General information:
BoundingBoxWidth = the maximum Width in pixels that any available character can take

BoundingBoxHeight = the maximum Height in pixels that any available character can take

BoundingBoxX = maximum x displacement (offset) in pixels that any available character can take

BoundingBoxY= maximum y displacement (offset) in pixels that any available character can take

AsciiStartOffset = This indicates the ASCII code of the first available character in the font data tables.

Font bounding box parameters can be useful for example to offset the starting print x and/or y position if we don't want to get pixels outside the screen position i.e. if we print at row 0, then given the offset, some pixels will be lost. Another instance where we can use it to calculate the new line position if we have to break a printed text. 

BBox.png

In this instance it is 48, which is "0". So every time print a character and we have to look in the font tables, we have to offset the position by AsciiStartOffset. In this example if we print "2" which is ASCII character 50, this would be actually 50-48=2 thus the third (0 is not Nothing  :) ) character in the font tables.

File Character tables:
BitmapOffset As Flash16 = this table indicates the 16-bit start position for each available character to look at in the Bitmap table. The 16-bit end position would be the position of the next character - 1. Here the last position in this table indicates the "virtual" start of the next "virtual" what would be additional character.

Bitmap As Flash8 = this tables holds the bytes who's bits will create each character. You read it using the start and stop values from BitmapOffset table.

DwidthOffsetX As Flash8 = this tables declares the device width of a character. In this case, after the character is rendered, the start of the next character is offset this value pixels on the X-axis from the current character origin. Note that the device width is not necessarily equal to the width of the character. It is simply the offset on the X-axis to move the current point to the start of the next character.

DwidthOffsetY As Flash8 = same as the previous but for Y-axis.

BbX As Flash8 = this tables declares the bounding box width for this character. This value is also used to calculate how many bytes per character pixel row from the Bitmap table we have to read and how many bits to print from those bytes.

BbY As Flash8 = this tables declares the bounding box height for this character. This indicates how many rows of pixels the character has.

BBOffsetX As Flash8= the lower left-hand corner of the character bitmap is offset on the X-axis from 0 by the value in this table.

BBOffsetY As Flash8= the lower left-hand corner of the character bitmap is offset on the Y-axis from 0 by the value in this table.

Note: Bitmap bytes are printed left-to-right top-to-bottom.

Example:
Print character "2" � ASCII code 50 --> "2" – AsciiStartOffset = 50-48=2 in the table

From tables BBOffsetX As Flash8 and BBOffsetY As Flash8 we read the current character offsets, let's call them ChOffsetX and ChOffsetY and the bounding box for this character ChBbX and ChBbY from tables BbX As Flash8 and BbY As Flash8

To print a character (or text) starting at position X_Pos,Y_Pos

1. We offset the position by the font max offset thus
X_Pos=X_Pos+Abs(BoundingBoxX)-1
Y_Pos=Y_Pos+Abs(BoundingBoxY)-1
   
2. Set a graphic memory window to fill - X_Start,X_End,Y_start,Y_End :
SetWindow(X_Pos + ChOffsetX, X_Pos + ChOffsetX + ChBbX - 1, Y_Pos + ChOffsetY, Y_Pos + ChOffsetY + ChBbY - 1)

3. Calculate the bytes and pixels per row to read and print. From BbX As Flash8 we get the value $0B= 11. So each row of pixels in our character will have 11 bits, thus we have to read 2 bytes from Bitmap As Flash8 table per row of pixels in the character.

4. From BbY As Flash8 we get the value $13=19. We have 19 rows for this character. And we will need 2 bytes per row, thus 38 bytes in total. (this step is not really needed)
   
5. From BitmapOffset As Flash16 we get the start position - is two thus $0037=55 and end position is three thus ($005D – 1)=93-1=92 so 55 to 92 are 38 bytes - same as calculated in step 4.
   
6. From Bitmap As Flash8 we start printing from byte 55 to byte 92 ($01, $80, $06, $E0,$0C, $60, $18, $60, $18, $60, $18, $60, $00, $60, $00, $C0, $01, $C0, $01, $80, $03, $00, $06, $00, $0C, $00, $18, $00, $30, $00, $60, $00, $FF, $80, $7F, $80, $40, $80) left-to-right top-to-bottom.

And we start filling the graphic memory with 2 bytes / 11 bits per row where 1 = pixel is set, 0 pixel is cleared, the rest of the bits per row are discarded.

00000001100 00000
00000110111 00000
00001100011 00000
00011000011 00000
00011000011 00000
00011000011 00000
00000000011 00000
00000000110 00000
00000001110 00000
00000001100 00000
00000011000 00000
00000110000 00000
00001100000 00000
00011000000 00000
00110000000 00000
01100000000 00000
11111111100 00000
01111111100 00000
01000000100 00000

If we have to print more characters, we no longer need to offset the character positions by the font offset BoundingBoxX  and BoundingBoxY. The new position is the current position + the values from DwidthOffsetX As Flash8 and DwidthOffsetY As Flash8 for the current character i.e $0B and $00.

trastikata

Tool update 1_02: BDF_DATA_CONVERT_1_02.zip

Now the tool creates indexed font files (tables and symbols) to simplify the use of multiple fonts. This might be needed since this type of variable size glyph can't be resized, at least not easy.

For example if you need different size digits then you actually have to include different font files (could be font files with the digits only).

v1_02_S.JPG

v1_02_F.JPG