Skip to content

Commit c3204d0

Browse files
committed
Formatting
1 parent 294b8b1 commit c3204d0

8 files changed

Lines changed: 124 additions & 101 deletions

File tree

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# v0.2.0
2+
3+
- Renamed `FullAgbFont`/`PrintableAgbFont` to `FullFont`/`PrintableFont`
4+
- `full_font!` and `printable_font!` macros now accept optional `iwram`/`ewram` placement flags
5+
- Fixed `wrap_at` semantics: now consistently a max line width in pixels relative to `pos`
6+
- Fixed line-wrapping to check before drawing (previously drew the overflowing char at end of line)
7+
- Fixed `size_of` to carry the wrapping character's width to the new line
8+
- Removed `draw_glyph` from IWRAM (only `blit_pixel_row_arm` needs to be there)
9+
110
# v0.1.0
211

3-
- Initial version
12+
- Initial version

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "gba_agb_font_renderer"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2024"
55
authors = ["Emma Britton <emmabritton@pm.me>"]
66
description = "Bitmap font renderer for GBA/AGB"

README.md

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,101 @@
1-
[![Crates.io](https://img.shields.io/crates/v/gba_agb_font_eb_renderer)](https://crates.io/crates/gba_agb_font_eb_renderer "Crates.io version")
2-
[![Documentation](https://img.shields.io/docsrs/gba_agb_font_eb_renderer)](https://docs.rs/gba_agb_font_eb_renderer "Documentation")
1+
[![Crates.io](https://img.shields.io/crates/v/gba_agb_font_renderer)](https://crates.io/crates/gba_agb_font_renderer "Crates.io version")
2+
[![Documentation](https://img.shields.io/docsrs/gba_agb_font_renderer)](https://docs.rs/gba_agb_font_renderer "Documentation")
33

44
# Bitmap Font for GBA/AGB
55

6-
## AGB Font Converter
6+
Renderer for bitmap fonts on the GBA using the [`agb`](https://github.com/agbrs/agb) framework
77

8-
[Converter](https://github.com/emmabritton/agb_font_converter)
8+
## Font converter
99

10-
## Font binary format
10+
Use the [AGB Font Converter](https://github.com/emmabritton/agb_font_converter) to convert images to fonts
1111

12-
Two modes are supported, selected by the `mode` byte at offset 0.
12+
## Usage
1313

14-
### Common header (all modes)
14+
### Loading a font
1515

16-
| Offset | Size | Field |
17-
|--------|------|-----------------------------------------------------|
18-
| 0 | 1 | `mode``0` = alphanum, `1` = all-256 |
19-
| 1 | 1 | `glyph_width` — width of the glyph cell in pixels |
20-
| 2 | 1 | `glyph_height` — height of the glyph cell in pixels |
16+
Two font types are available: `FullFont` (all 256 code points) and `PrintableFont` (95 printable ASCII chars, 0x20–0x7E). Load them at compile time with the corresponding macro:
2117

22-
### Mode 0 — printable ASCII (95 glyphs)
23-
24-
Supports all 95 printable ASCII characters: 0x20 (space) through 0x7E (tilde).
18+
```rust
19+
use gba_agb_font_renderer::prelude::*;
2520

26-
Glyphs are stored in ascending byte-value order (space, `!`, `"`, … `~`), so the compact glyph index is `char - 0x20`.
21+
// Full 256-char font from ROM (default)
22+
let font = full_font!(include_bytes!("my_font.bin"));
2723

28-
| Offset | Size | Field |
29-
|--------|---------------------------------------|---------------------------------------------------------------------------------------|
30-
| 3 | 95 | `char_widths` — advance width for each of the 95 characters, in ascending byte order |
31-
| 98 | 2 | padding (aligns data to a 4-byte boundary) |
32-
| 100 | `glyph_width × glyph_height × 95 / 2` | pixel data |
24+
// Printable ASCII font, placed in IWRAM for faster blitting
25+
// (~8× fewer waitstates per pixel row — only worthwhile for small fonts ~3 KB)
26+
let font = printable_font!(include_bytes!("my_font.bin"), iwram);
3327

34-
### Mode 1 — all-256 (256 glyphs)
28+
// Full 256-char font placed in EWRAM
29+
let font = full_font!(include_bytes!("my_font.bin"), ewram);
30+
```
3531

36-
All 256 Latin-1 code points, indexed directly by byte value.
32+
### Rendering text
3733

38-
| Offset | Size | Field |
39-
|--------|----------------------------------------|----------------------------------------------------------------------|
40-
| 3 | 256 | `char_widths` — per-character advance width, one byte per code point |
41-
| 259 | 1 | padding (aligns data to a 4-byte boundary) |
42-
| 260 | `glyph_width × glyph_height × 256 / 2` | pixel data |
34+
`wrap_at` is a maximum line width in pixels (relative to `pos`). Pass `None` to disable wrapping.
4335

44-
### Pixel data
36+
```rust
37+
use gba_agb_font_renderer::prelude::*;
38+
use agb::fixnum::vec2;
4539

46-
Stored as `u32` values, each holding 8 pixels at 4 bits per pixel (4bpp). Each row of a glyph occupies `(glyph_width + 7) / 8` `u32`s, so the full glyph table is `(glyph_width + 7) / 8 × glyph_height × glyph_count` `u32`s.
40+
let mut renderer = TextRenderer::default();
41+
renderer.draw_text(b"Hello, world!", &font, &mut bg, vec2(8, 16), Some(200), 0);
42+
```
4743

48-
## Usage
44+
### Typewriter effect
4945

50-
Embed a font at compile time using the `agb_font!` macro:
46+
`TypewriterRenderer` reveals text one chunk at a time. Call `update()` once per frame and `draw()` to blit newly revealed characters.
5147

5248
```rust
53-
let font = agb_font!(include_bytes!("my_font.bin"));
49+
use gba_agb_font_renderer::prelude::*;
50+
use agb::fixnum::vec2;
51+
52+
// 1 character revealed every 2 frames, wrap at 200px
53+
let mut tw = TypewriterRenderer::new(1, 2, Some(200));
54+
tw.load_text(b"Hello, world!", vec2(8, 16));
55+
56+
loop {
57+
tw.update();
58+
tw.draw(&font, &mut bg, 0);
59+
if tw.is_complete() { break; }
60+
vblank.wait_for_vblank();
61+
}
5462
```
5563

56-
Place the font data in IWRAM for faster blitting (~8× fewer waitstates per pixel row read).
57-
Only worthwhile for small fonts such as alphanum mode (~3 KB):
64+
To clear the text area before loading new text:
5865

5966
```rust
60-
let font = agb_font!(include_bytes!("my_font.bin"), iwram);
67+
tw.clear(&font, &mut bg, 0);
68+
tw.load_text(b"New message", vec2(8, 16));
6169
```
6270

63-
Remap palette colour indices (e.g. change colour 15 to colour 1):
71+
## Font binary format
72+
73+
Two modes are supported, selected by the `mode` byte at offset 0.
6474

65-
```rust
66-
let font = agb_font_remapped!(include_bytes!("my_font.bin"), [15 => 1]);
67-
// multiple remappings:
68-
let font = agb_font_remapped!(include_bytes!("my_font.bin"), [15 => 1, 14 => 2]);
69-
// with IWRAM source:
70-
let font = agb_font_remapped!(include_bytes!("my_font.bin"), [15 => 1], iwram);
71-
```
75+
### Common header
7276

73-
Construct from typed data (all-256 mode only):
77+
| Offset | Size | Field |
78+
|--------|------|-----------------------------------------------|
79+
| 0 | 1 | `mode``0` = printable ASCII, `1` = all-256 |
80+
| 1 | 1 | `glyph_width` — glyph cell width in pixels |
81+
| 2 | 1 | `glyph_height` — glyph cell height in pixels |
7482

75-
```rust
76-
let font = AgbFont::new(8, 12, CHAR_WIDTHS, &PIXEL_DATA);
77-
```
83+
### Mode 0 — printable ASCII (95 glyphs)
84+
85+
| Offset | Size | Field |
86+
|--------|--------|-------------------------------------------------------------------|
87+
| 3 | 95 | `char_widths` — advance width per character, ascending byte order |
88+
| 98 | 2 | padding to 4-byte boundary |
89+
| 100 | varies | 4bpp pixel data |
90+
91+
### Mode 1 — all-256 (256 glyphs)
92+
93+
| Offset | Size | Field |
94+
|--------|--------|----------------------------------------------|
95+
| 3 | 256 | `char_widths` — advance width per code point |
96+
| 259 | 1 | padding to 4-byte boundary |
97+
| 260 | varies | 4bpp pixel data |
98+
99+
### Pixel data
78100

79-
For runtime loading from a byte slice, use `AgbFont::from_bytes`.
101+
Stored as `u32` values, each holding 8 pixels at 4 bits per pixel (4bpp). Each glyph row occupies `(glyph_width + 7) / 8` `u32`s. Palette index 0 is transparent.

src/font/full.rs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
pub const GLYPH_COUNT_FULL: usize = 256;
22
pub const HEADER_ALL: usize = 256 + 1 + 1 + 1 + 1;
33

4-
/// 4bpp font for gba
5-
/// Has the all 256 ASCII chars
6-
/// Points to font data in cart
4+
/// 4bpp font for gba covering all 256 Latin-1 code points.
5+
/// Font data location (ROM/IWRAM/EWRAM) is determined by the `full_font!` macro.
76
pub struct FullFont {
87
pub char_widths: [u8; GLYPH_COUNT_FULL],
98
pub data: &'static [u32],
@@ -40,7 +39,6 @@ impl FullFont {
4039
pub const fn from_static_bytes(bytes: &'static [u8]) -> Self {
4140
assert!(bytes.len() >= HEADER_ALL, "font bytes too short");
4241
let mode = bytes[0];
43-
assert!(mode <= 1, "unknown font mode");
4442
assert!(mode == 1, "invalid font mode (must be 1 for full font)");
4543

4644
let glyph_width = bytes[1];
@@ -64,16 +62,7 @@ impl FullFont {
6462
"font pixel data length is not a multiple of 4"
6563
);
6664
let data: &'static [u32] = unsafe {
67-
let (ptr, total_len): (*const u8, usize) = core::mem::transmute(bytes);
68-
let data_len = total_len - HEADER_ALL;
69-
assert!(
70-
data_len.is_multiple_of(4),
71-
"font pixel data length is not a multiple of 4"
72-
);
73-
core::mem::transmute::<(*const u32, usize), &'static [u32]>((
74-
ptr.add(HEADER_ALL) as *const u32,
75-
data_len / 4,
76-
))
65+
core::slice::from_raw_parts(bytes.as_ptr().add(HEADER_ALL) as *const u32, data_len / 4)
7766
};
7867

7968
let glyph_size = row_u32s * glyph_height as usize;

src/font/mod.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@ use crate::prelude::{FullFont, PrintableFont};
33
pub mod full;
44
pub mod printable;
55

6-
pub const PALETTE_SIZE: usize = 16;
7-
pub const ROWS_PER_NORMAL: usize = 8;
8-
pub const ROWS_PER_LARGE: usize = 24;
9-
106
/// 4bpp font for gba
117
pub trait AgbFont {
128
fn char_widths(&self) -> &[u8];
@@ -26,8 +22,10 @@ pub trait AgbFont {
2622

2723
fn row_u32s(&self) -> usize;
2824

29-
/// Return the pixel data for the glyph corresponding to `c`, or `None` if the font
30-
/// does not contain a glyph for that character.
25+
/// Return the pixel data for the glyph corresponding to `c`.
26+
///
27+
/// Panics in debug builds if `c` is outside the font's character range.
28+
/// In release builds, out-of-range values produce undefined pixel data.
3129
fn glyph(&self, c: u8) -> &[u32] {
3230
if cfg!(debug_assertions) && self.char_offset() != 0 && !(32..=126).contains(&c) {
3331
panic!("glyph {c} out of printable bounds");
@@ -52,17 +50,23 @@ pub trait AgbFont {
5250
let mut total_height: u32 = self.glyph_height();
5351

5452
for &c in text {
55-
let char_w = self.char_width(c) as u32;
56-
57-
if current_line_width + char_w > max_width || c == b'\n' {
53+
if c == b'\n' {
5854
if current_line_width > max_encountered_width {
5955
max_encountered_width = current_line_width;
6056
}
61-
6257
current_line_width = 0;
6358
total_height += self.glyph_height();
6459
} else {
65-
current_line_width += char_w;
60+
let char_w = self.char_width(c) as u32;
61+
if current_line_width + char_w > max_width {
62+
if current_line_width > max_encountered_width {
63+
max_encountered_width = current_line_width;
64+
}
65+
current_line_width = char_w;
66+
total_height += self.glyph_height();
67+
} else {
68+
current_line_width += char_w;
69+
}
6670
}
6771
}
6872

src/font/printable.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ pub const GLYPH_COUNT_PRINTABLE: usize = 95;
44
pub const PRINTABLE_ASCII_START: u8 = 0x20;
55
pub const PRINTABLE_ASCII_END: u8 = 0x7E;
66

7-
/// 4bpp font for gba
8-
/// Has the 95 printable ASCII chars
9-
/// Points to font data
7+
/// 4bpp font for gba covering the 95 printable ASCII characters (0x20–0x7E).
8+
/// Font data location (ROM/IWRAM/EWRAM) is determined by the `printable_font!` macro.
109
pub struct PrintableFont {
1110
pub char_widths: [u8; GLYPH_COUNT_PRINTABLE],
1211
pub data: &'static [u32],
@@ -19,7 +18,6 @@ impl PrintableFont {
1918
pub const fn from_static_bytes(bytes: &'static [u8]) -> Self {
2019
assert!(bytes.len() >= HEADER_PRINTABLE, "font bytes too short");
2120
let mode = bytes[0];
22-
assert!(mode <= 1, "unknown font mode");
2321
assert!(
2422
mode == 0,
2523
"invalid font mode (must be 0 for printable font)"
@@ -46,16 +44,10 @@ impl PrintableFont {
4644
"font pixel data length is not a multiple of 4"
4745
);
4846
let data: &'static [u32] = unsafe {
49-
let (ptr, total_len): (*const u8, usize) = core::mem::transmute(bytes);
50-
let data_len = total_len - HEADER_PRINTABLE;
51-
assert!(
52-
data_len.is_multiple_of(4),
53-
"font pixel data length is not a multiple of 4"
54-
);
55-
core::mem::transmute::<(*const u32, usize), &'static [u32]>((
56-
ptr.add(HEADER_PRINTABLE) as *const u32,
47+
core::slice::from_raw_parts(
48+
bytes.as_ptr().add(HEADER_PRINTABLE) as *const u32,
5749
data_len / 4,
58-
))
50+
)
5951
};
6052

6153
let glyph_size = row_u32s * glyph_height as usize;

src/renderer.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,24 +40,25 @@ impl TextRenderer {
4040
}
4141
cursor_x = pos.x;
4242
} else {
43-
self.draw_glyph(
44-
font.glyph(c),
45-
font,
46-
background,
47-
cursor_x,
48-
cursor_y,
49-
palette_id,
50-
);
51-
cursor_x += font.char_width(c) as i32;
43+
let char_w = font.char_width(c) as i32;
5244
if let Some(wrap_at) = wrap_at
53-
&& cursor_x >= wrap_at
45+
&& cursor_x - pos.x + char_w > wrap_at
5446
{
5547
cursor_y += font.glyph_height() as i32;
5648
if cursor_x > longest {
5749
longest = cursor_x;
5850
}
5951
cursor_x = pos.x;
6052
}
53+
self.draw_glyph(
54+
font.glyph(c),
55+
font,
56+
background,
57+
cursor_x,
58+
cursor_y,
59+
palette_id,
60+
);
61+
cursor_x += char_w;
6162
}
6263
}
6364
if cursor_x > longest {
@@ -66,7 +67,6 @@ impl TextRenderer {
6667
(cursor_x - pos.x, cursor_y - pos.y, longest - pos.x)
6768
}
6869

69-
#[unsafe(link_section = ".iwram")]
7070
fn draw_glyph<T: AgbFont>(
7171
&mut self,
7272
glyph: &[u32],
@@ -136,6 +136,10 @@ impl TextRenderer {
136136
self.tiles.len() - 1
137137
}
138138

139+
/// Clear a pixel region by zeroing the tile rows it covers.
140+
///
141+
/// Note: zeros entire 8-pixel-wide tile rows, so pixels in tiles that extend
142+
/// outside the given rect are also cleared.
139143
pub fn clear_pixel_rect(
140144
&mut self,
141145
background: &mut RegularBackground,

src/typewriter.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use alloc::vec::Vec;
1111
/// characters to the background.
1212
///
1313
/// ```ignore
14-
/// let mut tw = TypewriterRenderer::new(1, 2); // 1 char every 2 frames
14+
///# use agb::fixnum::vec2;
15+
///
16+
/// let mut tw = TypewriterRenderer::new(1, 2, Some(200)); // 1 char every 2 frames, wrap at 200px
1517
/// tw.load_text(b"Hello, world!", vec2(8, 16));
1618
///
1719
/// loop {
@@ -110,6 +112,7 @@ impl TypewriterRenderer {
110112
self.chars_revealed = self.text.len();
111113
}
112114

115+
/// Returns `true` once all characters have been revealed.
113116
pub fn is_complete(&self) -> bool {
114117
self.chars_revealed >= self.text.len()
115118
}

0 commit comments

Comments
 (0)