Project 6: Font Rendering Engine

Render crisp text at multiple sizes with glyph metrics, caching, and partial updates.

Quick Reference

Attribute Value
Difficulty Level 3: Advanced
Time Estimate 1-2 weeks
Main Programming Language C (Alternatives: Rust, MicroPython)
Alternative Programming Languages Rust, MicroPython
Coolness Level Level 4: Hardcore
Business Potential 2. The “Diagnostics” Level
Prerequisites Project 2 primitives, basic bitmap handling
Key Topics Glyph metrics, bitmap fonts, caching, dirty rectangles

1. Learning Objectives

By completing this project, you will:

  1. Parse bitmap font data and render glyphs to a framebuffer.
  2. Implement baseline alignment, kerning, and spacing.
  3. Build a glyph cache to speed up repeated text.
  4. Use dirty rectangles to update only changed text regions.
  5. Render multiple font sizes with consistent layout.

2. All Theory Needed (Per-Concept Breakdown)

2.1 Glyph Metrics and Text Layout

Fundamentals

Text rendering is not just drawing pixels; it is arranging glyphs using font metrics. Each glyph has a width, height, bearing (offset from baseline), and advance (how far to move the pen). The baseline is the imaginary line on which letters sit. If you ignore metrics, your text will look uneven, with letters floating or overlapping. A font engine uses these metrics to place each glyph correctly and compute line height and spacing. Even with bitmap fonts, you must respect advance and bearings to achieve clean text.

Deep Dive into the concept

A glyph is the visual representation of a character. The font provides metrics that define how the glyph aligns relative to the baseline and origin. The bearing (often left and top bearings) tells you how far to offset the glyph from the pen position. The advance tells you how far to move after drawing the glyph. Consider the letters “g” and “A”: “g” descends below the baseline, while “A” does not. Without baseline alignment, “g” would appear too high or clipped. Line height is often defined as ascent + descent + line gap. For embedded fonts, you might store ascent/descent in the font header. When laying out text, you start at (x, y baseline), draw each glyph at (x + bearing_x, y - bearing_y), then advance x by advance_x. Kerning adds small adjustments for specific glyph pairs (e.g., “AV”). On embedded systems, you may skip kerning for simplicity but should at least include uniform spacing and baseline alignment.

Another challenge is scaling. Bitmap fonts don’t scale cleanly; you typically use separate bitmap sizes or a 1-bit font scaled by integer multiples. For this project, use two bitmap sizes (e.g., 8x8 and 16x16). Layout must consider the font size’s ascent/descent; otherwise, multi-size text will overlap. A deterministic layout engine ensures text metrics are consistent across frames, which prevents flicker when UI updates.

How this fits on projects

Metrics are essential in Section 3.2 and Section 5.10 Phase 2. They are also used in Project 9 (system monitor) and Project 11 (score display). Also used in: Project 9, Project 11.

Definitions & key terms

  • Baseline -> Reference line where text sits.
  • Advance -> How far to move the pen after drawing a glyph.
  • Bearing -> Offset from pen to glyph bitmap.
  • Ascent/Descent -> Height above/below baseline.

Mental model diagram (ASCII)

   ascent
    |
    |  A
    |__ baseline ____
    |   g
    |   descent

How it works (step-by-step)

  1. Set pen position on baseline.
  2. For each character, look up glyph metrics.
  3. Draw glyph at (x + bearing_x, y - bearing_y).
  4. Advance x by advance_x.

Failure modes:

  • No baseline -> uneven text.
  • Ignored bearing -> letters overlap.

Minimal concrete example

void draw_text(const char *s, int x, int y) {
  int pen_x = x;
  while (*s) {
    glyph_t g = font_lookup(*s++);
    draw_glyph(g, pen_x + g.bearing_x, y - g.bearing_y);
    pen_x += g.advance;
  }
}

Common misconceptions

  • “All glyphs are the same width.” -> Not in proportional fonts.
  • “Baseline is optional.” -> It defines consistent alignment.

Check-your-understanding questions

  1. What is the difference between bearing and advance?
  2. Why does “g” extend below the baseline?
  3. How do you compute line height?

Check-your-understanding answers

  1. Bearing is glyph offset; advance is pen movement.
  2. It has a descender in its design.
  3. Ascent + descent + line gap.

Real-world applications

  • LCD dashboards with readable text
  • Menu systems in embedded products

Where you’ll apply it

  • This project: Section 3.2, Section 5.10 Phase 2
  • Also used in: Project 9

References

  • FreeType documentation (metrics)
  • Bitmap font tutorials

Key insights

Text quality is mostly about metrics, not pixels.

Summary

Correct metrics make text readable and professional.

Homework/Exercises to practice the concept

  1. Render “Agjp” and verify baseline alignment.
  2. Adjust advance values and observe spacing.
  3. Compute line height for two font sizes.

Solutions to the homework/exercises

  1. The descenders should extend below the baseline.
  2. Increasing advance widens spacing.
  3. Use ascent + descent + line gap from font header.

2.2 Bitmap Font Rasterization and Caching

Fundamentals

Bitmap fonts store glyphs as pixel grids. Rendering text means copying these pixels into the framebuffer. A font cache stores pre-rendered glyphs or rows to avoid repeated work. On embedded systems, caching is essential if you redraw the same text repeatedly (e.g., UI labels, FPS counters). A cache can store glyph bitmaps in RAM or in a packed 1-bit format that is expanded during rendering.

Deep Dive into the concept

Bitmap fonts are straightforward: each glyph is a small bitmap (often 1-bit-per-pixel). Rendering involves iterating over the bitmap and setting pixels in the framebuffer for each “on” bit. This is fast for a small number of characters, but expensive if you redraw the same text each frame. A glyph cache stores frequently used glyphs in expanded RGB565 format, allowing fast blits. The trade-off is memory usage. For example, caching 96 ASCII glyphs at 16x16 in RGB565 uses ~96 * 16 * 16 * 2 ~= 49 KB, which is feasible.

Caching also enables effects like bold or inverted text: you can precompute variants and store them. Another optimization is to store glyphs as 1-bit and expand on the fly, but this costs CPU. A hybrid approach caches only frequently used glyphs (digits, UI labels), while rare glyphs are rendered on demand. The cache also interacts with dirty rectangles: if you only update the rectangle that contains changed text, you avoid redrawing the entire screen. For example, a changing FPS counter only invalidates a small region. A deterministic caching strategy keeps frame times predictable.

How this fits on projects

Glyph caching is central to Section 3.2 and Section 5.10 Phase 3. It is also used in Project 9 (system monitor) and Project 11 (game score display). Also used in: Project 9, Project 11.

Definitions & key terms

  • Bitmap font -> Glyphs stored as pixel grids.
  • Glyph cache -> Stored, ready-to-blit glyph bitmaps.
  • 1bpp -> One bit per pixel.
  • Dirty rectangle -> Region to redraw.

Mental model diagram (ASCII)

Glyph bitmap (1bpp) -> expand -> RGB565 cache -> blit to framebuffer

How it works (step-by-step)

  1. Look up glyph bitmap and metrics.
  2. If cached, blit cached RGB565 glyph.
  3. If not cached, expand bitmap to RGB565 and store.
  4. Mark text region as dirty and update LCD.

Failure modes:

  • Cache too small -> thrashing.
  • No caching -> slow text updates.

Minimal concrete example

uint16_t cache[GLYPH_W * GLYPH_H];
for (int y = 0; y < h; y++)
  for (int x = 0; x < w; x++)
    cache[y*w + x] = (bitmap[y] & (1 << x)) ? fg : bg;

Common misconceptions

  • “Text rendering cost is negligible.” -> It dominates many embedded UIs.
  • “Caching is only for large fonts.” -> Digits change often; cache them.

Check-your-understanding questions

  1. Why cache digits in an FPS counter?
  2. What is the memory cost of caching 96 glyphs at 16x16?
  3. What happens if cache is too small?

Check-your-understanding answers

  1. Digits redraw every frame; caching saves CPU.
  2. About 49 KB in RGB565.
  3. Cache thrashes, losing performance benefits.

Real-world applications

  • Instrument panels with static labels
  • Menu systems and dashboards

Where you’ll apply it

  • This project: Section 3.2, Section 5.10 Phase 3
  • Also used in: Project 9

References

  • Bitmap font rendering guides
  • LVGL font subsystem

Key insights

Caching converts expensive text rendering into a fast blit.

Summary

A small glyph cache can transform UI performance.

Homework/Exercises to practice the concept

  1. Cache digits 0-9 and compare rendering speed.
  2. Implement a cache eviction policy (LRU or FIFO).
  3. Measure frame time with and without caching.

Solutions to the homework/exercises

  1. Digits update faster with cache hits.
  2. FIFO is simplest and deterministic.
  3. Expect large improvements in FPS.

3. Project Specification

3.1 What You Will Build

A font rendering engine that supports at least two font sizes, baseline alignment, and a glyph cache. The LCD shows a text dashboard with static labels and dynamic values.

3.2 Functional Requirements

  1. Font parser for bitmap fonts.
  2. Text layout with baseline and spacing.
  3. Glyph cache for fast rendering.
  4. Dirty updates for changed text regions.

3.3 Non-Functional Requirements

  • Performance: 60 updates per second for a changing counter.
  • Reliability: No text flicker or overlapping glyphs.
  • Usability: Simple API draw_text(x,y,"hello").

3.4 Example Usage / Output

CPU: 42%
FPS: 60
Temp: 33C

3.5 Data Formats / Schemas / Protocols

  • Bitmap font stored as 1bpp arrays
  • Glyph metrics stored in a font header

3.6 Edge Cases

  • Characters outside font range
  • Negative bearing values
  • Mixed font sizes in same line

3.7 Real World Outcome

The LCD displays crisp text at two sizes. A changing FPS counter updates without flicker and only the numeric region is redrawn.

3.7.1 How to Run (Copy/Paste)

cd LEARN_RP2350_LCD_DEEP_DIVE/font_engine
mkdir -p build
cd build
cmake ..
make -j4
cp font_engine.uf2 /Volumes/RP2350

3.7.2 Golden Path Demo (Deterministic)

  • Render “HELLO” at size 8 and 16.
  • Update FPS counter once per second.
  • No flicker; background remains static.

3.7.3 Failure Demo (Deterministic)

  • Disable baseline alignment.
  • Expected: letters appear uneven, descenders clipped.
  • Fix: restore metrics-based layout.

4. Solution Architecture

4.1 High-Level Design

[Text Layout] -> [Glyph Cache] -> [Blit] -> [Framebuffer] -> LCD

4.2 Key Components

| Component | Responsibility | Key Decisions | |———–|—————-|—————| | Font loader | Parse glyph data | Use fixed-size bitmap fonts | | Layout engine | Position glyphs | Baseline + advance | | Cache | Store expanded glyphs | Cache digits and common glyphs |

4.3 Data Structures (No Full Code)

typedef struct { uint8_t w,h; int8_t bearing_x, bearing_y; uint8_t advance; } glyph_t;

4.4 Algorithm Overview

Key Algorithm: Text Rendering

  1. For each character, lookup glyph metrics.
  2. Fetch cached bitmap or expand 1bpp.
  3. Blit to framebuffer at baseline position.

Complexity Analysis:

  • Time: O(pixels in glyphs)
  • Space: O(cache size)

5. Implementation Guide

5.1 Development Environment Setup

# Use pico-sdk plus font assets

5.2 Project Structure

font_engine/
- src/
  - font.c
  - glyph_cache.c
  - text_layout.c
  - main.c
- assets/
  - font8x8.h

5.3 The Core Question You’re Answering

“How do I render clean, fast text on a tiny LCD?”

5.4 Concepts You Must Understand First

  1. Glyph metrics and baseline alignment
  2. Bitmap font storage
  3. Caching and dirty rectangles

5.5 Questions to Guide Your Design

  1. Which glyphs should be cached first?
  2. How will you handle unsupported characters?
  3. How will you update only changed text?

5.6 Thinking Exercise

Draw a baseline and place letters “Agjp” to visualize descenders.

5.7 The Interview Questions They’ll Ask

  1. What is a glyph advance?
  2. Why do descenders matter?
  3. How do caches improve performance?

5.8 Hints in Layers

  • Hint 1: Start with monospaced fonts.
  • Hint 2: Add baseline math after basic blitting works.
  • Hint 3: Cache digits for dynamic counters.

5.9 Books That Will Help

| Topic | Book | Chapter | |——-|——|———| | Rendering | “Computer Graphics from Scratch” | Text rendering sections |

5.10 Implementation Phases

Phase 1: Basic Glyph Blitting (3-4 days)

Goals: Render ASCII text. Tasks: Implement bitmap font blit. Checkpoint: Static text displays correctly.

Phase 2: Metrics and Layout (3-4 days)

Goals: Baseline alignment. Tasks: Use advance/bearing values. Checkpoint: Text looks aligned.

Phase 3: Caching and Dirty Updates (3-4 days)

Goals: Improve speed. Tasks: Add cache and dirty rectangles. Checkpoint: FPS counter updates smoothly.

5.11 Key Implementation Decisions

| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Font type | Monospace vs proportional | Monospace first | Simpler baseline | | Cache strategy | Full vs partial | Partial | Memory savings |


6. Testing Strategy

6.1 Test Categories

| Category | Purpose | Examples | |———-|———|———-| | Unit Tests | Glyph math | advance, bearing checks | | Integration Tests | Text layout | multi-line render | | Performance Tests | FPS counter | 60 Hz update |

6.2 Critical Test Cases

  1. Baseline test: render “Agjp”.
  2. Numeric counter: update once per frame.
  3. Clip test: text at screen edge.

6.3 Test Data

Font: 8x8 ASCII subset

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

| Pitfall | Symptom | Solution | |———|———|———-| | Ignored bearings | Uneven text | Use metrics | | No caching | Slow updates | Cache digits | | Full redraw | Flicker | Dirty rectangles |

7.2 Debugging Strategies

  • Draw baseline line to verify alignment.
  • Render bounding boxes for glyphs.

7.3 Performance Traps

  • Per-pixel font rendering each frame; cache to avoid it.

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add inverted text mode.

8.2 Intermediate Extensions

  • Add proportional font support.

8.3 Advanced Extensions

  • Implement UTF-8 subset with extended glyphs.

9. Real-World Connections

9.1 Industry Applications

  • On-device diagnostics
  • Embedded UI widgets
  • LVGL font subsystem

9.3 Interview Relevance

  • Text rendering and caching are common UI topics.

10. Resources

10.1 Essential Reading

  • Bitmap font tutorials
  • FreeType metrics documentation

10.2 Video Resources

  • Embedded graphics text rendering talks

10.3 Tools & Documentation

  • Font conversion scripts

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain advance vs bearing.
  • I can describe a glyph cache.

11.2 Implementation

  • Text renders cleanly at two sizes.
  • FPS counter updates without flicker.

11.3 Growth

  • I can explain my font engine in an interview.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Render static ASCII text with baseline alignment.

Full Completion:

  • Caching and dirty updates for dynamic text.

Excellence (Going Above & Beyond):

  • Proportional font support and kerning.