SND vs BDF Fonts

 visits
Parent Previous Next

Choose the format for the job

Positron Font Foundry exports two bitmap-font formats for TFT & EPD Graphic Library 2. Both store ready-to-draw pixels in a Positron BASIC .inc file, so your microcontroller does not need to interpret a Windows font.

The main difference is spacing: SND gives every character the same box; BDF stores a separate ink rectangle and advance for each character.

This guide starts with what that means on a display, then explains the generated tables for readers integrating or modifying a drawing routine. For application controls and the batch workflow, see the User Guide.


Question

SND

BDF

How much horizontal space does a character use?

The fixed cell width.

Its stored advance, which can differ from its visible width.

Do digits stay in predictable positions?

Yes, each position has a fixed cell.

Depends on the font's advances; proportional text can shift.

What is it useful for?

Clocks, counters, aligned values and status fields.

Labels, menus and naturally spaced text.

How is a character found in the file?

Calculate its position from the fixed data size.

Look up its start in an offset table.

Can one character be replaced easily?

Yes, clear and redraw that cell.

Often simpler to clear and redraw the whole text area.

Which uses less flash?

No per-character geometry tables, but every cell is stored in full.

Extra tables, but tightly cropped character bitmaps can save space.


Neither format is always the smallest or fastest overall. SND has simpler addressing. BDF may draw fewer pixels for narrow characters. Font size, character range, display interface and your drawing routine determine the practical result.

A simple spacing example

Imagine displaying Wi.. In a 16-pixel-wide SND font, all three characters advance by 16 pixels, for 48 pixels in total.

In an illustrative BDF font, their advances might be 18, 5 and 4 pixels, for 27 pixels in total. Those numbers depend on the selected font and size. The advance is the movement to the next character, not necessarily the width of the visible ink.

BDF can also store a monospaced font: per-character advances are supported, but they do not have to be different. The exported tables contain individual advances, not pair-specific kerning instructions.

Size: three measurements that should not be confused

Measurement

Meaning

Render size

The scale used to convert the original font to pixels. In BDF mode the application converts points at 96 DPI.

Ink box

The smallest rectangle containing one character's visible pixels. Its width and height vary with the shape.

Advance

How far the pen moves horizontally after drawing the character. It includes the intended spacing.


For example, 20 pt converts to 27 px in the application. A BDF file can therefore be named F2_BDF_27px_Arial_Regular.inc even though a period occupies only a few pixels and a capital occupies many more. The filename's size does not come from the first character, an average, or a maximum ink box.

SND is different: F2_SND_16x24_Arial_Regular.inc means that every stored cell is exactly 16 × 24 pixels.

Slots, filenames and sets

A slot number identifies the font tables inside a file. For example, slot 2 produces table names ending in 2.

Make Set can repeat a slot range when preparing more fonts than there are slots. This is useful for exporting alternatives: several files may use F2 while having different font names. Only one of those F2 alternatives should be included in the same firmware build. Distinct filenames do not make their internal table identifiers distinct.

Make Set checks both INC and PNG filenames before adding jobs. It checks the current output folder, existing queue and proposed set, including collisions caused by removing spaces and punctuation from font names. This is a queue-time filename check, not a guarantee against later overwrites. Rendering settings such as weight, tracking and character range are not all represented in the filename.

SND: a fixed box for every character

SND stores complete cells one after another, including blank space around the ink. Since every cell takes the same number of bytes, the drawing routine can jump directly to a character's data.

Header and bitmap table

In the following examples, <slot> and other angle-bracket values are placeholders, not literal Positron BASIC syntax.

$define FontSnd<slot>

Symbol FontSndPixels<slot> = <cell width>

Symbol FontSndSpace<slot>  = <cell height>

Symbol FontSndOffset<slot> = <first codepoint>


Dim TableFont<slot> As Flash8 = ...


Name

What it tells the renderer

FontSndPixels

The width of every cell, in pixels.

FontSndSpace

The height of every cell, in pixels.

FontSndOffset

The character number corresponding to the first stored cell.

TableFont

The pixel data for every cell, in character-number order.


The index is codepoint − firstCodepoint, not the codepoint itself. For a font beginning at 48, character 48 is entry 0.

Pixel storage

Each byte holds eight horizontal pixels. Bit 7 is the leftmost pixel, and bit 0 is the rightmost. A set bit means ink; a clear bit means no ink. The drawing routine chooses the actual display colours.

Rows are stored top to bottom. Each row uses a whole number of bytes, with unused low bits at the end set to zero.

bytesPerRow   = ceil(cellWidth / 8)

bytesPerGlyph = bytesPerRow × cellHeight

glyphIndex    = codepoint − firstCodepoint

startByte     = glyphIndex × bytesPerGlyph

A 16 × 16 cell uses 2 bytes per row and 32 bytes per character. A 10 × 16 cell also uses 2 bytes per row: its six unused bits at the end of each row do not represent extra display pixels.

After drawing, the pen moves by the cell width. For character position N, counting from zero in a single unwrapped line:

penX(N) = textStartX + N × cellWidth

This predictable placement makes SND convenient for replacing one digit. Clear the entire old cell, then draw the new digit. Drawing only the new ink without clearing the old ink can leave unwanted pixels, even with SND.

0x vs. $ — the placeholders above aren't literal

One literal detail worth knowing if you ever diff or hand-edit a generated file: the two formats write their byte values with different hex prefixes — SND emits 0xXX, BDF emits $XX (and $XXXX for the 16-bit BitmapOffset entries). Both are ordinary Positron BASIC hex literals; the difference is only which generator module produced the text, not a difference in what the numbers mean.

BDF: a separate ink box and spacing for each character

The application's BDF mode is inspired by the Bitmap Distribution Format's character measurements. Its output is a set of Positron BASIC flash tables, not an X11 .bdf text file.

Only each character's ink rectangle is stored, together with the information needed to position it relative to the pen and baseline. Think of the baseline as the line most letters sit on; parts of letters such as g and p extend below it.

Font-level header

$define Font<slot>

Symbol BoundingBoxWidth<slot>  = <largest character ink width>

Symbol BoundingBoxHeight<slot> = <largest character ink height>

Symbol BoundingBoxX<slot>      = <smallest horizontal bearing>

Symbol BoundingBoxY<slot>      = <smallest vertical bearing>

Symbol AsciiStartOffset<slot>  = <first codepoint>

These values summarise the selected range. Width and height are calculated as separate maxima over all characters; X and Y are separate minima. They may come from different characters.

They are not the dimensions of a shared cell or necessarily a complete baseline-aligned envelope. In particular, the tallest individual character need not span the combined top of an accented capital and bottom of a descender. Use the per-character measurements for positioning; do not infer exact line spacing from BoundingBoxHeight alone.

Character tables

Each name below has the slot number appended in the actual file.


Table

Stored as

Purpose

BitmapOffset

Flash16

The starting byte of each character in Bitmap, plus one final total.

Bitmap

Flash8

All character bitmaps joined together.

DwidthOffsetX

Signed values in Flash8

Horizontal pen advance after drawing.

DwidthOffsetY

Signed values in Flash8

Vertical advance; generated as zero for horizontal text.

BbX

Unsigned Flash8

This character's ink width.

BbY

Unsigned Flash8

This character's ink height.

BBOffsetX

Signed values in Flash8

Distance from the pen to the left edge of the ink.

BBOffsetY

Signed values in Flash8

Height of the bottom of the ink relative to the baseline.

Why there is an offset table

Different-sized bitmaps occupy different amounts of storage. Entry i in BitmapOffset gives the start of character i; entry i+1 gives its end.

glyphIndex = codepoint − AsciiStartOffset

startByte  = BitmapOffset[glyphIndex]

byteCount  = BitmapOffset[glyphIndex + 1] − startByte

For N characters there are N+1 offsets. The extra final entry is the total bitmap byte count, allowing the last character's length to be calculated in the same way as all the others.

Inside each bitmap, packing is the same as SND: rows run top to bottom, bit 7 is leftmost, and each row is padded to a whole number of bytes. Its row width comes from that character's BbX.

Bearings and advance, in plain language

Horizontal bearing (BBOffsetX) tells you where the ink begins relative to the pen. A negative value means that part of the character extends to the left of the pen, as can happen with italic shapes.

Vertical bearing (BBOffsetY) places the bottom of the ink relative to the baseline. Zero places it at the baseline; positive values lift it above the baseline; negative values let it extend below. The sign has this meaning regardless of the screen's coordinate system.

Advance (DwidthOffsetX) moves the pen to the next character. Do not substitute BbX for this value or add tracking again: the application's Tracking setting is already included in the exported advance. Ink can extend beyond an advance, so neighbouring characters can overlap.

Pixel editing rebuilds the ink rectangle and bitmap offsets as needed, while retaining the character's advance. Making a letter wider by painting extra pixels does not automatically increase the space after it.

Computing a real line envelope

If your drawing routine needs a genuine line envelope — to size a text field or cursor box before any string is known — do not reach for BoundingBoxHeight/BoundingBoxY alone. BoundingBoxHeight is the largest single character's own ink height, and it can easily come from a character sitting close to the baseline, while the true highest point above the baseline may belong to a different, shorter-but-higher character (an accented capital, for instance). The application itself derives its own preview envelope directly from the per-character tables instead:

ascent  = max over i of ( BBOffsetY[i] + BbY[i] )   ' highest ink point above baseline

descent = max(0, -min over i of BBOffsetY[i])        ' deepest ink point below baseline

lineHeight = ascent + descent

This is the same pair of values the pixel editor and preview grid use to size their shared editing cell, and it is the calculation to replicate on the MCU side for an accurate combined envelope rather than one character's own bounding box.

Blank glyphs are not zero-length

A blank character, such as a space, is not stored as a zero-length bitmap entry: the generator gives it a minimal 1×1 ink box (BbX = BbY = 1) with every bit clear, so it still occupies one byte in Bitmap and one full-size step in BitmapOffset, while carrying its own positive DwidthOffsetX. A drawing routine does not need to special-case a zero-byte glyph, but it will see this 1-pixel placeholder if it inspects the bitmap directly.

Drawing reference

The following is pseudocode for understanding the stored data, not a drop-in PIC BASIC routine. Check that a codepoint belongs to the font before indexing, and clip pixel coordinates to the display.

Read signed values correctly

Some BDF tables hold signed 8-bit values even though their storage is declared Flash8. Interpret bytes 128–255 as negative numbers:

signedValue = storedByte

if storedByte >= 128:

    signedValue = storedByte − 256

For example, $FF means −1 in a bearing or advance table, but 255 in an unsigned dimension table. Use sufficiently wide signed arithmetic for coordinates and sums; a negative bearing should not wrap into a large positive position.

SND coordinates

For the library's bottom-left origin, with Y increasing upward, let cellTopY be the coordinate of the top pixel row:

pixelX = penX + column

pixelY = cellTopY − storedRow

Visit only columns less than the cell width, ignoring the padding bits. Then move penX by the fixed cell width.

BDF coordinates

Using the same bottom-left origin, let baselineY be the row coordinate to which the signed vertical bearing is applied. The edges below are inclusive pixel positions:

left      = penX + signed(BBOffsetX[i])

bottom    = baselineY + signed(BBOffsetY[i])

top       = bottom + BbY[i] − 1

rowBytes  = ceil(BbX[i] / 8)

startByte = BitmapOffset[i]


for row = 0 to BbY[i] − 1:

    for column = 0 to BbX[i] − 1:

        byteValue = Bitmap[startByte + row × rowBytes + floor(column / 8)]

        bitNumber = 7 − (column mod 8)

        if bitNumber in byteValue is set:

            drawPixel(left + column, top − row)


penX = penX + signed(DwidthOffsetX[i])

A blank character's 1×1 placeholder bitmap draws no ink either way, but you may still choose to skip the loop when BbX[i] and BbY[i] are both 1 and the single byte is zero, as a cheap fast path. Always apply the advance regardless. The baseline remains the same while drawing a horizontal line.

For a top-left screen origin, convert the resulting pixel coordinate using yDown = screenHeight − 1 − yUp. This avoids changing the meaning of the stored bearings or accidentally reversing the bitmap rows.

Wrapping, line spacing, foreground/background colours and erasing old text belong to the drawing routine. These tables do not define a complete text-layout policy.

Replacing text already on screen

With SND, each character owns a predictable rectangular cell. Clear that cell before drawing its replacement. This supports small updates such as changing the last digit of a counter.

With proportional BDF, the pen position of character N is the start position plus the advances of the preceding characters in the string. These are not necessarily consecutive entries in the font table. Replacing a character with a different advance can move the rest of the text.

You can calculate or cache those positions, but the ink boxes may overlap and the replacement string may be shorter than the old one. A straightforward approach is to clear a known text field large enough for the old and new content, then redraw the complete new string. More selective updates are possible when the renderer carefully tracks positions and affected ink areas.

Storage and practical limits

For N SND characters, the bitmap size is:

N × ceil(cellWidth / 8) × cellHeight bytes

For BDF, the logical table payload is approximately:

sum of all character bitmap bytes

+ 2 × (N + 1) bytes for BitmapOffset

+ 6 × N bytes for advances, dimensions and bearings

These figures describe the table data, not the size of the human-readable .inc text file or final compiler-specific program-memory use. BDF's extra tables can be outweighed by storing smaller ink rectangles, so compare actual results for your font and range.

The current BDF writer uses 16-bit bitmap offsets and rejects total bitmap data above 65,535 bytes. Individual ink dimensions fit in unsigned bytes (0–255); advances and bearings fit in signed bytes (−128–127). Large point sizes or very broad ranges can exceed these limits. Reduce the size, reduce the range, or split the font into separate files when that happens.

What the application preview tells you

The grid is for inspecting character shapes. BDF characters appear inside a shared editing envelope; the grid's cell width is not the exported character advance.

The current TFT view is also a visual guide. It adds preview spacing and uses its own wrapping and line-height rules, so it should not be used as a pixel-exact prediction of firmware layout. For precise device rendering, use the exported advances and bearings and check the result with your target drawing routine.

The current Weight control changes strokes in discrete pixel steps, and Tracking rounds to whole pixels. Both are applied before export; the microcontroller receives the resulting pixels and spacing, not the slider values or a font-scaling instruction.

See also

Positron Font Foundry · User Manual

Created with the Personal Edition of HelpNDoc: Free iPhone documentation generator