diff --git a/agb-image-converter/Cargo.toml b/agb-image-converter/Cargo.toml
index 850090a0..6f845b2d 100644
--- a/agb-image-converter/Cargo.toml
+++ b/agb-image-converter/Cargo.toml
@@ -17,3 +17,4 @@ syn = "1"
 proc-macro2 = "1"
 quote = "1"
 asefile = "0.3.4"
+fontdue = "0.7"
diff --git a/agb-image-converter/src/font_loader.rs b/agb-image-converter/src/font_loader.rs
new file mode 100644
index 00000000..97dd521e
--- /dev/null
+++ b/agb-image-converter/src/font_loader.rs
@@ -0,0 +1,68 @@
+use crate::ByteString;
+use quote::quote;
+
+use proc_macro2::TokenStream;
+
+struct LetterData {
+    width: usize,
+    height: usize,
+    xmin: i32,
+    ymin: i32,
+    advance_width: f32,
+    rendered: Vec<u8>,
+}
+
+pub fn load_font(font_data: &[u8], pixels_per_em: f32) -> TokenStream {
+    let font = fontdue::Font::from_bytes(
+        font_data,
+        fontdue::FontSettings {
+            collection_index: 0,
+            scale: pixels_per_em,
+        },
+    )
+    .expect("Invalid font data");
+
+    let line_metrics = font.horizontal_line_metrics(pixels_per_em).unwrap();
+
+    let line_height = line_metrics.new_line_size as i32;
+    let ascent = line_metrics.ascent as i32;
+
+    let font = (0..128)
+        .map(|i| font.rasterize(char::from_u32(i).unwrap(), pixels_per_em))
+        .map(|(metrics, bitmap)| {
+            let width = metrics.width;
+            let height = metrics.height;
+
+            LetterData {
+                width,
+                height,
+                rendered: bitmap,
+                xmin: metrics.xmin,
+                ymin: metrics.ymin,
+                advance_width: metrics.advance_width,
+            }
+        })
+        .map(|letter_data| {
+            let data_raw = ByteString(&letter_data.rendered);
+            let height = letter_data.height as u8;
+            let width = letter_data.width as u8;
+            let xmin = letter_data.xmin as i8;
+            let ymin = letter_data.ymin as i8;
+            let advance_width = letter_data.advance_width.ceil() as u8;
+
+            quote!(
+                agb::display::FontLetter::new(
+                    #width,
+                    #height,
+                    #data_raw,
+                    #xmin,
+                    #ymin,
+                    #advance_width,
+                )
+            )
+        });
+
+    quote![
+        agb::display::Font::new(&[#(#font),*], #line_height, #ascent)
+    ]
+}
diff --git a/agb-image-converter/src/lib.rs b/agb-image-converter/src/lib.rs
index e4d8e954..e7885090 100644
--- a/agb-image-converter/src/lib.rs
+++ b/agb-image-converter/src/lib.rs
@@ -3,6 +3,7 @@ use proc_macro::TokenStream;
 use proc_macro2::Literal;
 use syn::parse::Parser;
 use syn::{parse_macro_input, punctuated::Punctuated, LitStr};
+use syn::{Expr, ExprLit, Lit};
 
 use std::path::PathBuf;
 use std::{iter, path::Path, str};
@@ -12,6 +13,7 @@ use quote::{format_ident, quote, ToTokens};
 mod aseprite;
 mod colour;
 mod config;
+mod font_loader;
 mod image_loader;
 mod palette16;
 mod rust_generator;
@@ -309,6 +311,58 @@ fn palete_tile_data(
     (palette_data, tile_data, assignments)
 }
 
+#[proc_macro]
+pub fn include_font(input: TokenStream) -> TokenStream {
+    let parser = Punctuated::<Expr, syn::Token![,]>::parse_separated_nonempty;
+    let parsed = match parser.parse(input) {
+        Ok(e) => e,
+        Err(e) => return e.to_compile_error().into(),
+    };
+
+    let all_args: Vec<_> = parsed.into_iter().collect();
+    if all_args.len() != 2 {
+        panic!("Include_font requires 2 arguments, got {}", all_args.len());
+    }
+
+    let filename = match &all_args[0] {
+        Expr::Lit(ExprLit {
+            lit: Lit::Str(str_lit),
+            ..
+        }) => str_lit.value(),
+        _ => panic!("Expected literal string as first argument to include_font"),
+    };
+
+    let font_size = match &all_args[1] {
+        Expr::Lit(ExprLit {
+            lit: Lit::Float(value),
+            ..
+        }) => value.base10_parse::<f32>().expect("Invalid float literal"),
+        Expr::Lit(ExprLit {
+            lit: Lit::Int(value),
+            ..
+        }) => value
+            .base10_parse::<i32>()
+            .expect("Invalid integer literal") as f32,
+        _ => panic!("Expected literal float or integer as second argument to include_font"),
+    };
+
+    let root = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get cargo manifest dir");
+    let path = Path::new(&root).join(&*filename);
+
+    let file_content = std::fs::read(&path).expect("Failed to read ttf file");
+
+    let rendered = font_loader::load_font(&file_content, font_size);
+
+    let include_path = path.to_string_lossy();
+
+    quote!({
+        let _ = include_bytes!(#include_path);
+
+        #rendered
+    })
+    .into()
+}
+
 #[cfg(test)]
 mod tests {
     use asefile::AnimationDirection;
diff --git a/agb/examples/dynamic_tiles.rs b/agb/examples/dynamic_tiles.rs
index aa70681b..4e70e2ff 100644
--- a/agb/examples/dynamic_tiles.rs
+++ b/agb/examples/dynamic_tiles.rs
@@ -15,17 +15,24 @@ fn main(mut gba: agb::Gba) -> ! {
 
     let mut bg = gfx.background(Priority::P0);
 
-    for y in 0..20u16 {
-        for x in 0..30u16 {
+    for y in 0..20u32 {
+        for x in 0..30u32 {
             let dynamic_tile = vram.new_dynamic_tile();
 
             for (i, bit) in dynamic_tile.tile_data.iter_mut().enumerate() {
-                *bit = ((((x + i as u16) % 8) << 4) | ((y + i as u16) % 8)) as u8
+                let i = i as u32;
+                let mut value = 0;
+
+                for j in 0..4 {
+                    value |= (value << 8) | (x + i) % 8 | ((y + j) % 8) << 4;
+                }
+
+                *bit = value;
             }
 
             bg.set_tile(
                 &mut vram,
-                (x, y).into(),
+                (x as u16, y as u16).into(),
                 &dynamic_tile.tile_set(),
                 TileSetting::from_raw(dynamic_tile.tile_index()),
             );
diff --git a/agb/examples/font/license.txt b/agb/examples/font/license.txt
new file mode 100644
index 00000000..8f7b275c
--- /dev/null
+++ b/agb/examples/font/license.txt
@@ -0,0 +1,17 @@
+Thanks for downloading one of codeman38's retro video game fonts, as seen on Memepool, BoingBoing, and all around the blogosphere.
+
+So, you're wondering what the license is for these fonts? Pretty simple; it's based upon that used for Bitstream's Vera font set <http://www.gnome.org/fonts/>.
+
+Basically, here are the key points summarized, in as little legalese as possible; I hate reading license agreements as much as you probably do:
+
+With one specific exception, you have full permission to bundle these fonts in your own free or commercial projects-- and by projects, I'm referring to not just software but also electronic documents and print publications.
+
+So what's the exception? Simple: you can't re-sell these fonts in a commercial font collection. I've seen too many font CDs for sale in stores that are just a repackaging of thousands of freeware fonts found on the internet, and in my mind, that's quite a bit like highway robbery. Note that this *only* applies to products that are font collections in and of themselves; you may freely bundle these fonts with an operating system, application program, or the like.
+
+Feel free to modify these fonts and even to release the modified versions, as long as you change the original font names (to ensure consistency among people with the font installed) and as long as you give credit somewhere in the font file to codeman38 or zone38.net. I may even incorporate these changes into a later version of my fonts if you wish to send me the modifed fonts via e-mail.
+
+Also, feel free to mirror these fonts on your own site, as long as you make it reasonably clear that these fonts are not your own work. I'm not asking for much; linking to zone38.net or even just mentioning the nickname codeman38 should be enough.
+
+Well, that pretty much sums it up... so without further ado, install and enjoy these fonts from the golden age of video games.
+
+[ codeman38 | cody@zone38.net | http://www.zone38.net/ ]
\ No newline at end of file
diff --git a/agb/examples/font/yoster.ttf b/agb/examples/font/yoster.ttf
new file mode 100644
index 00000000..bbbeb516
Binary files /dev/null and b/agb/examples/font/yoster.ttf differ
diff --git a/agb/examples/text_render.rs b/agb/examples/text_render.rs
new file mode 100644
index 00000000..39b9049b
--- /dev/null
+++ b/agb/examples/text_render.rs
@@ -0,0 +1,51 @@
+#![no_std]
+#![no_main]
+
+use agb::{
+    display::{tiled::TileSetting, Font, Priority},
+    include_font,
+};
+
+use core::fmt::Write;
+
+const FONT: Font = include_font!("examples/font/yoster.ttf", 12);
+
+#[agb::entry]
+fn main(mut gba: agb::Gba) -> ! {
+    let (gfx, mut vram) = gba.display.video.tiled0();
+    let vblank = agb::interrupt::VBlank::get();
+
+    vram.set_background_palette_raw(&[
+        0x0000, 0x0ff0, 0x00ff, 0xf00f, 0xf0f0, 0x0f0f, 0xaaaa, 0x5555, 0x0000, 0x0000, 0x0000,
+        0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+    ]);
+
+    let background_tile = vram.new_dynamic_tile().fill_with(0);
+
+    let mut bg = gfx.background(Priority::P0);
+
+    for y in 0..20u16 {
+        for x in 0..30u16 {
+            bg.set_tile(
+                &mut vram,
+                (x, y).into(),
+                &background_tile.tile_set(),
+                TileSetting::from_raw(background_tile.tile_index()),
+            );
+        }
+    }
+
+    vram.remove_dynamic_tile(background_tile);
+
+    let mut writer = FONT.render_text((0u16, 3u16).into(), 1, 2, &mut bg, &mut vram);
+
+    writeln!(&mut writer, "Hello, World!").unwrap();
+    writeln!(&mut writer, "This is a font rendering example").unwrap();
+
+    bg.commit();
+    bg.show();
+
+    loop {
+        vblank.wait_for_vblank();
+    }
+}
diff --git a/agb/src/display/font.rs b/agb/src/display/font.rs
new file mode 100644
index 00000000..031978cf
--- /dev/null
+++ b/agb/src/display/font.rs
@@ -0,0 +1,156 @@
+use core::fmt::{Error, Write};
+
+use crate::fixnum::Vector2D;
+use crate::hash_map::HashMap;
+
+use super::tiled::{RegularMap, TileSetting, VRamManager};
+
+pub struct FontLetter {
+    width: u8,
+    height: u8,
+    data: &'static [u8],
+    xmin: i8,
+    ymin: i8,
+    advance_width: u8,
+}
+
+impl FontLetter {
+    pub const fn new(
+        width: u8,
+        height: u8,
+        data: &'static [u8],
+        xmin: i8,
+        ymin: i8,
+        advance_width: u8,
+    ) -> Self {
+        Self {
+            width,
+            height,
+            data,
+            xmin,
+            ymin,
+            advance_width,
+        }
+    }
+}
+
+pub struct Font {
+    letters: &'static [FontLetter],
+    line_height: i32,
+    ascent: i32,
+}
+
+impl Font {
+    pub const fn new(letters: &'static [FontLetter], line_height: i32, ascent: i32) -> Self {
+        Self {
+            letters,
+            line_height,
+            ascent,
+        }
+    }
+
+    fn letter(&self, letter: char) -> &'static FontLetter {
+        &self.letters[letter as usize]
+    }
+}
+
+impl Font {
+    pub fn render_text<'a>(
+        &'a self,
+        tile_pos: Vector2D<u16>,
+        foreground_colour: u8,
+        background_colour: u8,
+        bg: &'a mut RegularMap,
+        vram_manager: &'a mut VRamManager,
+    ) -> TextRenderer<'a> {
+        TextRenderer {
+            current_x_pos: 0,
+            current_y_pos: 0,
+            font: self,
+            tile_pos,
+            vram_manager,
+            bg,
+            background_colour,
+            foreground_colour,
+        }
+    }
+}
+
+pub struct TextRenderer<'a> {
+    current_x_pos: i32,
+    current_y_pos: i32,
+    font: &'a Font,
+    tile_pos: Vector2D<u16>,
+    vram_manager: &'a mut VRamManager,
+    bg: &'a mut RegularMap,
+    background_colour: u8,
+    foreground_colour: u8,
+}
+
+impl<'a> Write for TextRenderer<'a> {
+    fn write_str(&mut self, text: &str) -> Result<(), Error> {
+        let mut tiles = HashMap::new();
+
+        let vram_manager = &mut self.vram_manager;
+        let foreground_colour = self.foreground_colour;
+        let background_colour = self.background_colour;
+
+        let mut render_pixel = |x: u16, y: u16| {
+            let tile_x = (x / 8) as usize;
+            let tile_y = (y / 8) as usize;
+            let inner_x = x % 8;
+            let inner_y = y % 8;
+
+            let colour = foreground_colour as u32;
+
+            let index = (inner_x + inner_y * 8) as usize;
+
+            let tile = tiles
+                .entry((tile_x, tile_y))
+                .or_insert_with(|| vram_manager.new_dynamic_tile().fill_with(background_colour));
+
+            tile.tile_data[index / 8] |= colour << ((index % 8) * 4);
+        };
+
+        for c in text.chars() {
+            if c == '\n' {
+                self.current_y_pos += self.font.line_height;
+                self.current_x_pos = 0;
+                continue;
+            }
+
+            let letter = self.font.letter(c);
+
+            let x_start = (self.current_x_pos + letter.xmin as i32).max(0);
+            let y_start =
+                self.current_y_pos + self.font.ascent - letter.height as i32 - letter.ymin as i32;
+
+            for letter_y in 0..(letter.height as i32) {
+                for letter_x in 0..(letter.width as i32) {
+                    let x = x_start + letter_x;
+                    let y = y_start + letter_y;
+
+                    let px = letter.data[(letter_x + letter_y * letter.width as i32) as usize];
+
+                    if px > 100 {
+                        render_pixel(x as u16, y as u16);
+                    }
+                }
+            }
+
+            self.current_x_pos += letter.advance_width as i32;
+        }
+
+        for ((x, y), tile) in tiles.into_iter() {
+            self.bg.set_tile(
+                self.vram_manager,
+                (self.tile_pos.x + x as u16, self.tile_pos.y + y as u16).into(),
+                &tile.tile_set(),
+                TileSetting::from_raw(tile.tile_index()),
+            );
+            self.vram_manager.remove_dynamic_tile(tile);
+        }
+
+        Ok(())
+    }
+}
diff --git a/agb/src/display/mod.rs b/agb/src/display/mod.rs
index 6d9ceb83..11c4f7be 100644
--- a/agb/src/display/mod.rs
+++ b/agb/src/display/mod.rs
@@ -23,6 +23,9 @@ pub mod tiled;
 /// Giving out graphics mode.
 pub mod video;
 
+mod font;
+pub use font::{Font, FontLetter};
+
 const DISPLAY_CONTROL: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0000) };
 pub(crate) const DISPLAY_STATUS: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0004) };
 const VCOUNT: MemoryMapped<u16> = unsafe { MemoryMapped::new(0x0400_0006) };
diff --git a/agb/src/display/tiled/mod.rs b/agb/src/display/tiled/mod.rs
index b1d239f8..a0ec32d8 100644
--- a/agb/src/display/tiled/mod.rs
+++ b/agb/src/display/tiled/mod.rs
@@ -6,7 +6,7 @@ mod vram_manager;
 pub use infinite_scrolled_map::{InfiniteScrolledMap, PartialUpdateStatus};
 pub use map::{MapLoan, RegularMap};
 pub use tiled0::Tiled0;
-pub use vram_manager::{TileFormat, TileIndex, TileSet, VRamManager};
+pub use vram_manager::{DynamicTile, TileFormat, TileIndex, TileSet, VRamManager};
 
 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
 #[repr(transparent)]
diff --git a/agb/src/display/tiled/vram_manager.rs b/agb/src/display/tiled/vram_manager.rs
index acf9d39f..fdb9538b 100644
--- a/agb/src/display/tiled/vram_manager.rs
+++ b/agb/src/display/tiled/vram_manager.rs
@@ -120,7 +120,21 @@ impl TileReferenceCount {
 
 #[non_exhaustive]
 pub struct DynamicTile<'a> {
-    pub tile_data: &'a mut [u8],
+    pub tile_data: &'a mut [u32],
+}
+
+impl DynamicTile<'_> {
+    pub fn fill_with(self, colour_index: u8) -> Self {
+        let colour_index = colour_index as u32;
+
+        let mut value = 0;
+        for i in 0..8 {
+            value |= colour_index << (i * 4);
+        }
+
+        self.tile_data.fill(value);
+        self
+    }
 }
 
 impl DynamicTile<'_> {
@@ -194,8 +208,15 @@ impl VRamManager {
             TileReferenceCount::new(TileInTileSetReference::new(&tile_set, index as u16));
 
         DynamicTile {
-            tile_data: &mut tiles
-                [index * tile_format.tile_size()..(index + 1) * tile_format.tile_size()],
+            tile_data: unsafe {
+                slice::from_raw_parts_mut(
+                    tiles
+                        .as_mut_ptr()
+                        .add((index * tile_format.tile_size()) as usize)
+                        .cast(),
+                    tile_format.tile_size() / core::mem::size_of::<u32>(),
+                )
+            },
         }
     }
 
diff --git a/agb/src/hash_map.rs b/agb/src/hash_map.rs
index 33db969d..be75786a 100644
--- a/agb/src/hash_map.rs
+++ b/agb/src/hash_map.rs
@@ -324,6 +324,39 @@ impl<'a, K, V> IntoIterator for &'a HashMap<K, V> {
     }
 }
 
+pub struct IterOwned<K, V> {
+    map: HashMap<K, V>,
+    at: usize,
+}
+
+impl<K, V> Iterator for IterOwned<K, V> {
+    type Item = (K, V);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        loop {
+            if self.at >= self.map.nodes.backing_vec_size() {
+                return None;
+            }
+
+            let maybe_kv = self.map.nodes.nodes[self.at].take_key_value();
+            self.at += 1;
+
+            if let Some((k, v, _)) = maybe_kv {
+                return Some((k, v));
+            }
+        }
+    }
+}
+
+impl<K, V> IntoIterator for HashMap<K, V> {
+    type Item = (K, V);
+    type IntoIter = IterOwned<K, V>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        IterOwned { map: self, at: 0 }
+    }
+}
+
 /// A view into an occupied entry in a `HashMap`. This is part of the [`Entry`] enum.
 pub struct OccupiedEntry<'a, K: 'a, V: 'a> {
     key: K,
@@ -908,7 +941,7 @@ mod test {
         let mut num_found = 0;
 
         for (_, value) in map.into_iter() {
-            max_found = max_found.max(*value);
+            max_found = max_found.max(value);
             num_found += 1;
         }
 
diff --git a/agb/src/lib.rs b/agb/src/lib.rs
index 245bc690..6fcd1bb9 100644
--- a/agb/src/lib.rs
+++ b/agb/src/lib.rs
@@ -114,6 +114,8 @@ pub use agb_image_converter::include_gfx;
 
 pub use agb_image_converter::include_aseprite_inner;
 
+pub use agb_image_converter::include_font;
+
 /// This macro declares the entry point to your game written using `agb`.
 ///
 /// It is already included in the template, but your `main` function must be annotated with `#[agb::entry]`, takes 1 argument and never returns.
diff --git a/book/games/pong/Cargo.lock b/book/games/pong/Cargo.lock
index d755da2e..7807f57f 100644
--- a/book/games/pong/Cargo.lock
+++ b/book/games/pong/Cargo.lock
@@ -40,6 +40,7 @@ name = "agb_image_converter"
 version = "0.7.0"
 dependencies = [
  "asefile",
+ "fontdue",
  "image",
  "proc-macro2",
  "quote",
@@ -67,6 +68,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "asefile"
 version = "0.3.4"
@@ -154,6 +166,36 @@ dependencies = [
  "miniz_oxide 0.4.4",
 ]
 
+[[package]]
+name = "fontdue"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
+dependencies = [
+ "hashbrown",
+ "ttf-parser",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash",
+]
+
 [[package]]
 name = "hound"
 version = "3.4.0"
@@ -277,6 +319,12 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "once_cell"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+
 [[package]]
 name = "png"
 version = "0.16.8"
@@ -366,8 +414,26 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "ttf-parser"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
diff --git a/examples/the-hat-chooses-the-wizard/Cargo.lock b/examples/the-hat-chooses-the-wizard/Cargo.lock
index 8e309664..3088d5c8 100644
--- a/examples/the-hat-chooses-the-wizard/Cargo.lock
+++ b/examples/the-hat-chooses-the-wizard/Cargo.lock
@@ -40,6 +40,7 @@ name = "agb_image_converter"
 version = "0.7.0"
 dependencies = [
  "asefile",
+ "fontdue",
  "image",
  "proc-macro2",
  "quote",
@@ -67,6 +68,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "asefile"
 version = "0.3.4"
@@ -154,6 +166,36 @@ dependencies = [
  "miniz_oxide 0.4.4",
 ]
 
+[[package]]
+name = "fontdue"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
+dependencies = [
+ "hashbrown",
+ "ttf-parser",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash",
+]
+
 [[package]]
 name = "hound"
 version = "3.4.0"
@@ -283,6 +325,12 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "once_cell"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+
 [[package]]
 name = "png"
 version = "0.16.8"
@@ -391,8 +439,26 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "ttf-parser"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
diff --git a/examples/the-purple-night/Cargo.lock b/examples/the-purple-night/Cargo.lock
index 3b7ff133..a2f79b5c 100644
--- a/examples/the-purple-night/Cargo.lock
+++ b/examples/the-purple-night/Cargo.lock
@@ -40,6 +40,7 @@ name = "agb_image_converter"
 version = "0.7.0"
 dependencies = [
  "asefile",
+ "fontdue",
  "image",
  "proc-macro2",
  "quote",
@@ -67,6 +68,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
 [[package]]
 name = "asefile"
 version = "0.3.4"
@@ -169,6 +181,16 @@ dependencies = [
  "miniz_oxide 0.4.4",
 ]
 
+[[package]]
+name = "fontdue"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a62391ecb864cf12ed06b2af4eda2e609b97657950d6a8f06841b17726ab253"
+dependencies = [
+ "hashbrown",
+ "ttf-parser",
+]
+
 [[package]]
 name = "generational-arena"
 version = "0.2.8"
@@ -178,6 +200,26 @@ dependencies = [
  "cfg-if 0.1.10",
 ]
 
+[[package]]
+name = "getrandom"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash",
+]
+
 [[package]]
 name = "hound"
 version = "3.4.0"
@@ -313,6 +355,12 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "once_cell"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+
 [[package]]
 name = "png"
 version = "0.16.8"
@@ -428,12 +476,30 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "ttf-parser"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c74c96594835e10fa545e2a51e8709f30b173a092bfd6036ef2cec53376244f3"
+
 [[package]]
 name = "unicode-xid"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
 
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
 [[package]]
 name = "xml-rs"
 version = "0.8.4"