Skip to content

Howto Create A Font

Arnaud Dupuis edited this page Oct 8, 2022 · 6 revisions

Howto: Create a font for the pygamelib

What is a font in the pygamelib?

A font is actually pretty simple: it is a collection of sprites that each represent a character of the alphabet.

Add to that a configuration file and you have a font. Quite straightforward!

Structure of a font package

Fonts that are packaged with the pygamelib are located in pygamelib/assets/fonts/.

Let's assume a font named DemoFont, it will be located at pygamelib/assets/fonts/DemoFont.

The font itself needs to be a Python package, its structure is (assuming the name DemoFont):

DemoFont/
    ┃
    ┣╸ __init__.py
    ┃
    ┣╸ config.json
    ┃
    ┣╸ glyphs.spr

Let's explain these files:

  • The __init__.py file just need to be here to declare a python package, it can be empty.
  • The config.json file is a small configuration file that gives a couple basic information to the Font class.
  • The glyphs.spr is the "pièce de résistance", it is a JSON file loadable by the SpriteCollection class. It contains all the glyphs of a font. A glyph can be a single letter or more, there is no software limit to the glyph identifier (maybe the size of a Python dict key).

The Font class will access and return the glyphs using the identifiers.

For example:

# Create a Font object and instantiate it with the 8bits font data.
font_8bits = Font('8bits')
# Create a Text object that uses the 8bits font
text = Text('Test text', font=font_8bits)
# Now print the text, formatted with the font (if you simply print(text) the terminal's font is used).
text.print_formatted()

The code above will produce the following output: Text with font example

While rendering this text, the Text class will use the Font.glyph() method to get the correct sprite to display. In the example, it will start with Font.glyph("T"), then Font.glyph("e") and so on and so forth.

All font MUST have a glyph named default. Whatever the language, whatever the type or any possible reason that you could have to create a custom font. If a font does not have the default glyph, the program using it will crash at the first unknown glyph requested.

Fortunately, it's very easy to choose an existing glyph as the default glyph.

The configuration file

The configuration file is very simple and minimalist. All options MUST be set.

Here is an example of config.json file for our DemoFont:

{
    "scalable": false,
    "monospace": true,
    "colorable": true,
    "height": 4,
    "horizontal_spacing": 1,
    "vertical_spacing": 1,
    "fg_color": {
        "red": 255,
        "green": 255,
        "blue": 255
    },
    "bg_color": null,
    "glyphs_map": {}
}

All of the options are declarative. It means that it declare something about the font. There's no magic behind (except for the colors, but we'll talk about it).

The options of the configuration file must all be present, and they are as follow:

  • scalable: declare that the font can be scaled, i.e: it is safe to use Font.glyph("a").scale(1.5). "Safe to use" means that the font will correctly display for values between 0.5 and 2.0. The terminal makes it difficult to guarantee that, so it is advised to set this value to False. It does not prevent the user to try to scale the font, but they have been warned.
  • monospace: declare that the font is monospace, i.e: all glyphs have the same width.
  • colorable: declare to the user that the font can be safely colored, i.e: Text.bg_color and Text.fg_color can safely be used.
  • height: declare the height of each glyphs. Since the width of each glyph can vary (especially if the font is not monospace), you have to query the width for each glyph with Font.glyph([name of a glyph]).width.
  • horizontal_spacing and vertical_spacing: Tell the font user how to render the glyphs in terms of margin around the glyph. The size, like all other sizes, is in number of characters in the terminal. Be careful that 1 character in height is often equal to 2 characters in width.
  • fg_color: Tell the Font class the foreground color of the font. Since a glyph in the font can be a mix of sprixel where the actual foreground and background color are used independently to make a glyph look good, the Font class uses the color of a sprixel to determine if it is a foreground or background sprixel. It is recommended to have a foreground color (like white) and potentially no background color (user will still be able to set a background color to their text of course).
  • bg_color: Same as fg_color but for the background color. When you design a font, aside from purpose built fonts, there's little justification to set a background color.
  • glyph_map: The glyph map is a key/value dictionary that allow you to map a glyph to an existing glyph in the font. For example, "a": "A" would map "a" to the "A" glyph that already exist in the font.

Here is another configuration example, this one is from the 8bits font that is part of pygamelib.asset.fonts:

{
    "scalable": false,
    "monospace": true,
    "colorable": true,
    "height": 4,
    "horizontal_spacing": 1,
    "vertical_spacing": 1,
    "fg_color": {
        "red": 255,
        "green": 255,
        "blue": 255
    },
    "bg_color": null,
    "glyphs_map": {
        "a": "A",
        "b": "B",
        "c": "C",
        "d": "D",
        "e": "E",
        "f": "F",
        "g": "G",
        "h": "H",
        "i": "I",
        "j": "J",
        "k": "K",
        "l": "L",
        "m": "M",
        "n": "N",
        "o": "O",
        "p": "P",
        "q": "Q",
        "r": "R",
        "s": "S",
        "t": "T",
        "u": "U",
        "v": "V",
        "w": "W",
        "x": "X",
        "y": "Y",
        "z": "Z"
    }
}

That font is not scalable, monospace (all glyph have the same width) and colorable (the user can use Text.fg_color or Text.bg_color and it will have the intended effect).

There's a 1 character space (margin) around the glyphs and all glyph have a height of 4 characters (in the console).

The color identifying the foreground sprixels is white (RGB 255,255,255), and there's no background color.

The glyph map is very simple: it is an all cap font. So all lower case letters are mapped to their upper case counterpart.

Tools to generate fonts

Fortunately, you don't have to do all this by hand. The pygamelib has 2 utilities that can help with the creation of a font: