Creating new fonts for Pokitto - part 2 - exporting from font editor to a C file

I’ve been using this python3 script (originally by @drummyfish, some modifications by me) to make custom fonts. The script takes a .png charmap and prints the font data which you can copy to your my_font.h file.

fontconv.py
#!/usr/bin/env python3

# Convert bitmap font to array. Usage:
#
# python3 fontconv.py charmap.png [only caps] [monospace width]
#
# original author: Miloslav Ciz
# modified by: jpfli
# license: CC0 1.0

import sys
from PIL import Image

# Configuration
firstChar = ord(' ')
lastChar = ord('\x7f')
lastCharCaps = ord(']')
indent = 4

# Command line parameters
if len(sys.argv) < 2:
  print("Usage: "+sys.argv[0]+" charmap.png [only caps] [monospace width]")
  exit(0)
onlyCaps = len(sys.argv) >= 3 and int(sys.argv[2]) != 0
monospaceWidth = 0 if len(sys.argv) < 4 else int(sys.argv[3])


image = Image.open(sys.argv[1])
pixels = image.load()

numChars = 1 + (lastCharCaps if onlyCaps else lastChar) - firstChar
charDiv = (image.size[0] + 1) // numChars
fontH = image.size[1]
fontHRound = ((fontH + 7)//8)*8

startCol = charDiv
endCol = 0

charArray = []
for i in range(numChars):
  columns = []
  for x in range(charDiv):
    bitcol = [0]*fontHRound
    for y in range(fontH):
      bitcol[y] = 1 if pixels[i*charDiv + x, y][0] == 0 else 0

    if sum(bitcol) != 0:
      if x + 1 > endCol:
        endCol = x + 1
      if x < startCol:
        startCol = x

    column = []
    while len(bitcol) >= 8:
      column.append(sum([bitcol[j]<<j for j in range(8)]))
      bitcol = bitcol[8:]
    columns.append(column)

  charArray.append(columns)

fontW = monospaceWidth if monospaceWidth > 0 else endCol - startCol

print(' '*indent + str(fontW) + ", " + str(fontH) + ", " + str(firstChar) + ", " + ('1' if onlyCaps else '0') + ", // width, height, start char, only caps")

for i in range(len(charArray)):
  columns = charArray[i]
  if len(columns) < fontW:
    columns += [[0]]*(fontW - len(columns))

  charW = monospaceWidth
  if monospaceWidth > 0:
    # strip empty columns from left side
    columns = columns[startCol:] + [[0]*(fontHRound//8)]*startCol
  else:
    nonZeroIndices = [index for index,value in enumerate([sum(col) for col in columns]) if value != 0]
    if len(nonZeroIndices) > 0:
      tmp = columns
      # strip empty columns from left side
      columns = columns[nonZeroIndices[0]:] + [[0]*(fontHRound//8)]*nonZeroIndices[0]
      charW = 1 + nonZeroIndices[-1] - nonZeroIndices[0]
    else:
      charW = fontW//2

  line = ""
  for x in range(fontW):
    for col in columns[x]:
      line += "0x" + format(col, '02X') + ", "

  line = "0x" + format(charW, '02X') + ", " + line
  line += " // " + str(firstChar + i) + ": '" + chr(firstChar + i) + "'"

  print(' '*indent + line)

Example charmap:
font-charmap


What comes to the problem with font height, I took a look in PokittoLib and there is a height limit of 16 pixels in the draw function bufferChar(x, y, index) (in PokittoFrameBuffer.cpp). But if you are having problems even with smaller font heights then there is some other problem.

Anyway, below is a version of the bufferChar(x, y, index) function that can handle any font height.

PokittoFrameBuffer.cpp bufferChar(x, y, index) function
int Display::bufferChar(int16_t x, int16_t y, uint16_t index){
    uint8_t w = font[0];
    uint8_t h = font[1];

    uint8_t hbytes=((h + 7)>>3); //GLCD fonts are arranged w+1 times h/8 bytes

    const uint8_t* bitmap = font + 4 + index*(1 + w*hbytes); //add an offset to the pointer

    int8_t i, j;
    uint8_t charW = *bitmap; //first byte of char is char width
    ++bitmap;
    // GLCD fonts are arranged LSB = topmost pixel of char, so its easy to just shift through the column
    uint32_t bitcolumn; //16 bits for 2x8 bit high characters

    if( fontSize != 2 ) fontSize = 1;

    void (*drawPixelFG)(int16_t,int16_t, uint8_t) = &Display::drawPixelNOP;
    void (*drawPixelBG)(int16_t,int16_t, uint8_t) = &Display::drawPixelNOP;
    if( x>=0 && y >= 0 && x+w*fontSize<width && y+(h+1)*fontSize<height ){
	if( color != invisiblecolor )
	    drawPixelFG = &Display::drawPixelRaw;
	if( bgcolor != invisiblecolor )
	    drawPixelBG = &Display::drawPixelRaw;
    }else{
	if( color != invisiblecolor )
	    drawPixelFG = &Display::drawPixel;
	if( bgcolor != invisiblecolor )
	    drawPixelBG = &Display::drawPixel;
    }

    void (*drawPixel[])(int16_t,int16_t, uint8_t) = {drawPixelBG, drawPixelFG};
    uint8_t colors[] = {static_cast<uint8_t>(bgcolor), static_cast<uint8_t>(color)};

#if PROJ_ARDUBOY > 0
#else
    if( fontSize != 2 ){
#endif

        for (i = 0; i < charW; ++i) {
            for(uint32_t byteNum = 0; byteNum < hbytes; ++byteNum) {
                bitcolumn = *bitmap;
                ++bitmap;
                uint32_t endRow = (8 + 8*byteNum < h) ? (8 + 8*byteNum) : h;
                for (j = 8*byteNum; j < endRow; ++j) { // was j<=h
                    uint8_t c = colors[ bitcolumn & 1 ];

#if PROJ_ARDUBOY > 0
                    drawPixel[ bitcolumn&1 ](x, y + 7 - j,c);
#elif PROJ_SUPPORT_FONTROTATION > 0
                    // if font flipping & rotation is allowed - do not slow down old programs!
                    if (flipFontVertical) {
                        drawPixel[ bitcolumn&1 ](x, y + h - j,c);
                    } else {
                        drawPixel[ bitcolumn&1 ](x, y + j,c);
                    }
#else
                    // "Normal" case
                    drawPixel[ bitcolumn&1 ](x, y + j,c);
#endif // PROJ_ARDUBOY
                    bitcolumn>>=1;
                }
            }
#if PROJ_SUPPORT_FONTROTATION > 0
            if (flipFontVertical) x--;
            else x++;
#else
            x++;
#endif
        }

#if PROJ_SUPPORT_FONTROTATION > 0
        if (flipFontVertical) return -charW-adjustCharStep;
        else return charW+adjustCharStep; // for character stepping
#else
        return charW+adjustCharStep;
#endif


#if PROJ_ARDUBOY > 0
#else
    }else{

	for (i = 0; i < charW; ++i) {
            for(uint32_t byteNum = 0; byteNum < hbytes; ++byteNum) {
                bitcolumn = *bitmap;
                ++bitmap;
                uint32_t endRow = (8 + 8*byteNum < h) ? (8 + 8*byteNum) : h;
                for (j = 8*byteNum; j < endRow; ++j) { // was j<=h
                    uint8_t c = colors[ bitcolumn & 1 ];

                    drawPixel[ bitcolumn&1 ](x + (i<<1)  , y + (j<<1), c);
                    drawPixel[ bitcolumn&1 ](x + (i<<1)+1, y + (j<<1), c);
                    drawPixel[ bitcolumn&1 ](x + (i<<1)  , y + (j<<1)+1, c);
                    drawPixel[ bitcolumn&1 ](x + (i<<1)+1, y + (j<<1)+1, c);
                    bitcolumn>>=1;
                }
	    }
	}

	return (charW+adjustCharStep)<<1;

    }
#endif // PROJ_ARDUBOY

}

With that fix, I tried the text outline method mentioned in another thread using a 50px high font on top of a 52px high outline font.

bigfont
Source code: bigfont.zip (32.1 KB)

3 Likes

Thanks! That is exactly what I need :slight_smile:

1 Like

That is a good improvement. Do you want to make a PR to PokittoLib?

How do I do that?

There’s same issue with the draw function used by TAS mode. That needs to be checked too.

You need to use Git for that. I can also do it if you send the changes to me.
Edit: PR=“pull request”

Added a PR against PokittoLib. It contains the forn fixes by @jpfli

  • support for big fonts (bigger than 16x16 pixels) for direct, buffered, and TAS modes
  • fixed font clipping in TAS mode at bottom and right screen edges
  • fixed fonnt clipping at the left edge
4 Likes

Merged, thanks @Hanski and @jpfli

1 Like

I updated fontconv.py script in my earlier post and added an example charmap image.

The script now ignores empty space between characters. I also moved some less frequently used command line parameters as variables inside the script file.

Fixed fontconv.py script in the earlier post:

  • Changed i to j as i is already used in the upper loop:
    column = []
    while len(bitcol) >= 8:
          column.append(sum([bitcol[j]<<j for j in range(8)]))  # Changed i to j
          bitcol = bitcol[8:]
2 Likes