Skip to main content

Module getting_started

Module getting_started 

Source
Available on crate feature articles only.
Expand description

Guide for spf.rs basics and understanding the file format via Rust.

§Getting Started in Rust

§Synopsis

spf.rs brings the world of SimplePixelFonts into the programming realm. Written in the Rust programming language, spf.rs aims to be effective and simple to use, providing a native crate for Rust and also an FFI compatible with C-like languages and C-interoperable languages. In particular, spf.rs provides a low-level interface to the binary representation of SimplePixelFont files via the crate::core module.

§Resources

It’s important that before beginning you have a general understanding of the Rust programming language. Additionally, this guide will explain the Rust representation of a SimplePixelFont which aims to closely reflect the binary file representation. However, you are encouraged to learn and use the official SPF specifications as an additional reference.

§The crate::core module

The crate::core module contains the lowest-level structures to represent a SimplePixelFont file. Included is the core::Layout struct which is the binary representation of a SimplePixelFont file as a Rust structure. A core::Layout holds a list of tables such as; core::CharacterTable, core::PixmapTable, core::ColorTable, and core::FontTable. Each holding its own records. Every one of these structs is #[non_exhaustive], so you build them with ::default() and then assign the fields you need. Let’s build a minimal font with one character, "w", backed by one pixmap:

use spf::core::*;

let mut pixmap = Pixmap::default();
pixmap.data = vec![0b10111111, 0b01010110]; // A 5x3, 1-bit-per-pixel glyph, packed LSB-first.

let mut pixmap_table = PixmapTable::default();
pixmap_table.configuration_flags = PixmapTableConfigurationFlags::ConstantWidth
    | PixmapTableConfigurationFlags::ConstantHeight
    | PixmapTableConfigurationFlags::ConstantBitsPerPixel;
pixmap_table.constant_width = Some(5);
pixmap_table.constant_height = Some(3);
pixmap_table.constant_bits_per_pixel = Some(1);
pixmap_table.link_flags = PixmapTableLinkFlags::LinkColorTables;
pixmap_table.color_table_indexes = Some(vec![0]); // Links to color_tables[0] below.
pixmap_table.pixmaps = vec![pixmap];

let mut character = Character::default();
character.code_points = String::from("w"); // May be made up of multiple utf8 characters, like "😊".
character.pixmap_index = Some(0); // Which pixmap in the linked PixmapTable this character uses.

let mut character_table = CharacterTable::default();
character_table.modifier_flags = CharacterTableModifierFlags::UsePixmapIndex;
character_table.link_flags = CharacterTableLinkFlags::LinkPixmapTables;
character_table.pixmap_table_indexes = Some(vec![0]); // Links to pixmap_tables[0] below.
character_table.characters = vec![character];

let mut layout = Layout::default();
layout.compact = true; // Strips padding bits in certain fields when converting the struct to data, saving space.
layout.pixmap_tables = vec![pixmap_table];
layout.character_tables = vec![character_table];

A few things worth calling out:

Side Note: To learn more about the different configuration flags and modifier flags, check out the SPF File Specifications.

§But what is a character in SimplePixelFonts?

Lets further discuss what a character is in the context of a SimplePixelFont font.

In simple terms a character in SimplePixelFont is simply a Character::code_points string, which may be made up of multiple utf8 characters such as a, <, 😊, é, etc. A character optionally carries an Character::advance_x, and explicitly links to its glyph via Character::pixmap_index. Additionally, if the table links to more than one PixmapTable, Character::pixmap_table_index can be used to specify the exact table the pixmap comes from.

The glyph itself lives in a Pixmap’s data which is a packed vector of bits, one per pixel is shown in this article, but can be up to 8 bits per pixel for up to 256 different colors. We will get into Color tables in a minute.

Pixels are stored row-major, origin top-left, left to right then top to bottom, packed least-significant-bit-first within each byte. Pixel 0 is the lowest bit of data[0], and so on. Here is a diagram which maps each pixel of a character to their index in the pixmap:

image link

And this will result in the following character:

image link

§Adding Color

Each pixel’s bits form a value that indexes into a linked core::ColorTable’s ColorTable::colors. We can add a color table to supply actual colors for our pixmaps:

let mut transparent = Color::default();
transparent.custom_alpha = Some(0); // Fully transparent.

let mut opaque = Color::default();
opaque.custom_alpha = Some(255); // Fully opaque.
opaque.red = 20;
opaque.green = 118;
opaque.blue = 192;

let mut color_table = ColorTable::default();
color_table.colors = vec![transparent, opaque];

layout.color_tables = vec![color_table];

Note:

  • PixmapTableLinkFlags::LinkColorTables plus PixmapTable::color_table_indexes links the pixmap table to a palette the same way CharacterTable links to PixmapTables. Our two colors mirror what spf.rs assumes when no ColorTable is linked at all: index 0 is transparent, everything else opaque. For monochrome fonts, like our example, a Color Table is not technically needed. However, for showcase we create one, plus now they’re real colors, and nothing stops you from adding a third or fourth Color. Just remember to raise constant_bits_per_pixel to fit them.

With our color table, pixel values of 0 index our transparent Color, and pixel values of 1 index our opaque rgb(20, 118, 192) Color. The renderer will use these colors as the default foreground and background colors for the text if they are not overridden.

image link

§Grouping characters into a font with the FontTable

A core::Layout can hold multiple CharacterTables. A core::FontTable is what groups them into a named, authored, versioned font. Think Regular/Bold/Italic variants of the same typeface, each pointing at its own CharacterTable(s). Let’s add one for the character table we already built:

let mut font = Font::default();
font.name = String::from("Sample Toy Font");
font.author = String::from("You");
font.version = 1;
font.linked_character_table_indexes = vec![0]; // Which CharacterTables this specific font uses.

let mut font_table = FontTable::default();
font_table.link_flags = FontTableLinkFlags::LinkCharacterTables;
font_table.character_table_indexes = Some(vec![0]); // CharacterTables available to fonts in this table.
font_table.fonts = vec![font];

layout.font_tables = vec![font_table];

Two link arrays are in play here, at two different levels: FontTable::character_table_indexes is the collection of CharacterTables available to every Font record in this table, while Font::linked_character_table_indexes is which tables from that set a specific font actually draws from. With one CharacterTable and one Font, both just point at index 0. However, a file with Regular/Bold/Italic fonts that share some character tables and not others is exactly what this two-level indirection is for, along with keeping the format architecture consistent.

That’s it! Layout now has everything: a Pixmap, a Character using it, and a Font naming the table that character lives in.

§Saving & Loading spf.rs fonts with std::fs

We can then encode the layout and use std::fs to write it to a file:

let data = layout_to_data(&layout).unwrap();

let mut file = std::fs::OpenOptions::new()
    .write(true)
    .create(true)
    .open("./sampleToyFont.spf")
    .unwrap();
file.write_all(&data).unwrap();

Or we can load an existing .spf file using std::fs as well:

let mut file = std::fs::OpenOptions::new()
    .read(true)
    .open("./sampleToyFont.spf")
    .unwrap();
let mut buffer: Vec<u8> = vec![];
file.read_to_end(&mut buffer).unwrap();
let layout = layout_from_data(&buffer).unwrap();