diff --git a/butano/include/bn_documentation.h b/butano/include/bn_documentation.h
index 20458c47d..f80d70402 100644
--- a/butano/include/bn_documentation.h
+++ b/butano/include/bn_documentation.h
@@ -924,6 +924,12 @@
*
* Showcase of a 4096x4096 world map with a perspective effect.
*
+ *
@ref font
+ *
+ * @image html examples_font.png
+ *
+ * Showcase of Butano BMFont support.
+ *
*
*/
@@ -1326,6 +1332,53 @@
*
* bn::sound_items::sfx.play();
* @endcode
+ *
+ *
+ * @section import_fonts Fonts
+ *
+ * By default fonts files go into the `fonts` folder of your project.
+ *
+ *
+ * @subsection import_bmfont Bitmap Font
+ *
+ * A bitmap font file (.fnt) in text format is supported. You can make it using Bitmap Font Generator, Bitmap Font Generator Online or BMfont-web.
+ *
+ * It is also a common font format used in generic game engines like Cocos, Unity and visual novel engines like Ren'Py.
+ *
+ * First of all, install Pillow.
+ *
+ * @code{.sh}
+ * # For MSYS2/MinGW-w64 users
+ * pacman -S mingw-w64-x86_64-python-pillow
+ * # For WSL2/Ubuntu/Debian users
+ * sudo apt-get install python3-pil
+ * # For Mac users
+ * brew install pillow
+ * # For FreeBSD users
+ * pkg install py38-pillow
+ * # For CentOS users
+ * yum install python3-pillow
+ * # For Fedora Linux users
+ * dnf install python3-pillow
+ * # For Arch Linux users
+ * pacman -S python37-pillow
+ * @endcode
+ *
+ * Place `*.fnt` and `*.png` files in the `fonts` folder. Notice that `*.png` files should have background color instead of transparent background.
+ *
+ * If the conversion process has finished successfully,
+ * a bunch of bn::sprite_font objects should have been generated in the `build` folder for all fonts files.
+ *
+ * For example, from two files named `items.fnt` and `items.png`,
+ * a header file named `items_sprite_font.h` is generated in the `build` folder.
+ *
+ * You can use these fonts to display text with only one line of C++ code:
+ *
+ * @code{.cpp}
+ * #include "items_sprite_font.h"
+ *
+ * bn::sprite_text_generator text_generator(items_sprite_font);
+ * @endcode
*/
@@ -1658,6 +1711,7 @@
* but the bn::sprite_font instances used in the examples don't provide japanese nor chinese characters,
* so you will have to make a new one with them.
*
+ * You can get ready-to-use free fonts here.
*
* @subsection faq_tonc_general_notes Are there some more general notes on GBA programming out there?
*
diff --git a/butano/include/bn_utf8_characters_map_ref.h b/butano/include/bn_utf8_characters_map_ref.h
index 70d0065d9..8621ed953 100644
--- a/butano/include/bn_utf8_characters_map_ref.h
+++ b/butano/include/bn_utf8_characters_map_ref.h
@@ -103,7 +103,7 @@ class utf8_characters_map_ref
++its;
}
- BN_ERROR("UTF-8 character not found");
+ BN_ERROR("UTF-8 character not found: ", key);
return 0;
}
diff --git a/butano/tools/butano_fonts_tool.py b/butano/tools/butano_fonts_tool.py
new file mode 100644
index 000000000..c1427d5f1
--- /dev/null
+++ b/butano/tools/butano_fonts_tool.py
@@ -0,0 +1,263 @@
+"""
+Copyright (c) 2022 laqieer laqieer@126.com
+zlib License, see LICENSE file.
+"""
+
+# Doc on BMFont: https://angelcode.com/products/bmfont/doc/file_format.html
+
+import os
+import sys
+import shlex
+import codecs
+import argparse
+
+from file_info import FileInfo
+from PIL import Image
+
+trim_fonts = False
+unique_characters = {' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'}
+
+
+def list_texts_files(texts_paths):
+ global trim_fonts
+ texts_file_names = []
+ texts_file_paths = []
+
+ if texts_paths is not None:
+ texts_path_list = texts_paths.split(' ')
+
+ for texts_path in texts_path_list:
+ if os.path.isfile(texts_path):
+ texts_file_name = os.path.basename(texts_path)
+ if FileInfo.validate(texts_file_name):
+ texts_file_names.append(texts_file_name)
+ texts_file_paths.append(texts_path)
+ elif os.path.isdir(texts_path):
+ folder_texts_file_names = sorted(os.listdir(texts_path))
+ for texts_file_name in folder_texts_file_names:
+ texts_file_path = texts_path + '/' + texts_file_name
+
+ if os.path.isfile(texts_file_path) and FileInfo.validate(texts_file_name):
+ texts_file_names.append(texts_file_name)
+ texts_file_paths.append(texts_file_path)
+
+ trim_fonts = len(texts_file_names) > 0
+
+ return texts_file_names, texts_file_paths
+
+
+def process_texts_files(texts_file_paths):
+ global unique_characters
+ for texts_file_path in texts_file_paths:
+ with open(texts_file_path, 'r', encoding='UTF-8') as texts_file:
+ for line in texts_file:
+ for char in line:
+ unique_characters.add(char)
+
+
+def list_fonts_files(fonts_folder_paths):
+ fonts_folder_path_list = fonts_folder_paths.split(' ')
+ fonts_file_names = []
+ fonts_file_paths = []
+
+ for fonts_folder_path in fonts_folder_path_list:
+ folder_fonts_file_names = sorted(os.listdir(fonts_folder_path))
+
+ for fonts_file_name in folder_fonts_file_names:
+ if fonts_file_name.endswith('.fnt'):
+ fonts_file_path = fonts_folder_path + '/' + fonts_file_name
+
+ if os.path.isfile(fonts_file_path) and FileInfo.validate(fonts_file_name):
+ fonts_file_names.append(fonts_file_name)
+ fonts_file_paths.append(fonts_file_path)
+
+ return fonts_file_names, fonts_file_paths
+
+
+def process_fonts_files(fonts_file_paths, build_folder_path):
+ global trim_fonts, unique_characters
+ fonts_graphics_path = build_folder_path + '/fonts/'
+ if not os.path.exists(fonts_graphics_path):
+ os.makedirs(fonts_graphics_path)
+ total_number = 0
+ for fonts_file_path in fonts_file_paths:
+ fonts_file_path_no_ext = os.path.splitext(fonts_file_path)[0]
+ fonts_folder_path, fonts_file_name_no_ext = os.path.split(fonts_file_path_no_ext)
+ font_name = fonts_file_name_no_ext + '_sprite_font'
+ fonts_header_path = build_folder_path + '/' + font_name + '.h'
+
+ with open(fonts_file_path, 'r') as fonts_file, open(fonts_header_path, 'w', encoding='utf-8') as header_file:
+ font_chars = []
+ font_widths = [0] * 95
+ unique_chars = unique_characters.copy()
+
+ for fonts_line in fonts_file:
+ line_type, *pair_tokens = shlex.split(fonts_line)
+ line_conf = dict(pair_token.split("=", 1) for pair_token in pair_tokens)
+
+ if line_type == "info":
+ padding_up, padding_right, padding_down, padding_left = [int(x) for x in line_conf['padding'].split(',')]
+ header_file.write('// ' + line_conf['face'].replace('"', '') + ' ' + line_conf['size'] + 'px')
+ if line_conf['bold'] != '0':
+ header_file.write(' bold')
+ if line_conf['italic'] != '0':
+ header_file.write(' italic')
+ header_file.write('\n')
+ header_file.write('\n')
+ elif line_type == "common":
+ font_height = int(line_conf['lineHeight'])
+ if font_height > 64:
+ raise ValueError('Font is too large')
+ elif font_height > 32:
+ font_height = 64
+ elif font_height > 16:
+ font_height = 32
+ elif font_height > 8:
+ font_height = 16
+ else:
+ font_height = 8
+ font_base = int(line_conf['base'])
+ font_y_offset = min(font_base - font_height, 0)
+ # Assume a font's width is not more than its height
+ font_width = font_height
+ font_pages = [None] * int(line_conf['pages'])
+ elif line_type == "page":
+ page_file_path = fonts_folder_path + '/' + line_conf['file'].replace('"', '')
+ font_pages[int(line_conf['id'])] = Image.open(page_file_path)
+ elif line_type == "chars":
+ font_number = int(line_conf['count'])
+ #fonts_image = Image.new('RGBA', (font_width, font_height * font_number))
+ transparent_color = font_pages[0].getpixel((0, 0))
+ fonts_image = Image.new('RGB', (font_width, font_height * (font_number + 94)), transparent_color)
+ elif line_type == "char":
+ if len(unique_characters) == 0:
+ break
+ font_code = int(line_conf['id'])
+ if not trim_fonts or chr(font_code) in unique_chars:
+ if trim_fonts:
+ unique_chars.remove(chr(font_code))
+ src_left = int(line_conf['x']) + padding_left
+ src_upper = int(line_conf['y']) + padding_up
+ src_right = int(line_conf['x']) + int(line_conf['width']) - padding_right
+ if src_right < src_left:
+ src_right = src_left
+ src_right = min(src_right, src_left + font_width)
+ src_lower = int(line_conf['y']) + int(line_conf['height']) - padding_down
+ if src_lower < src_upper:
+ src_lower = src_upper
+ src_upper = max(src_upper, src_lower - font_height)
+ dst_left = round(float(line_conf['xoffset'])) + padding_left
+ if dst_left < 0:
+ dst_left = 0
+ dst_right = dst_left + src_right - src_left
+ if dst_right > font_width:
+ dst_left -= min(dst_left, dst_right - font_width)
+ dst_upper = round(float(line_conf['yoffset'])) + padding_up
+ if dst_upper < 0:
+ dst_upper = 0
+ dst_lower = dst_upper + src_lower - src_upper
+ if dst_lower > font_height:
+ dst_upper -= min(dst_upper, dst_lower - font_height)
+ if dst_lower > 0:
+ dst_lower -= min(dst_lower, font_y_offset)
+ font_w = max(int(line_conf['xadvance']), int(line_conf['width']) - padding_left -padding_right)
+ font_w = min(font_w, font_width)
+ if font_code > 126:
+ font_chars.append(chr(font_code))
+ font_widths.append(font_w)
+ dst_upper += font_height * (len(font_widths) - 2)
+ elif font_code > 31:
+ font_widths[font_code - 32] = font_w
+ dst_upper += font_height * (font_code - 33)
+ if font_code > 32:
+ fonts_image.paste(font_pages[int(line_conf['page'])].crop((src_left, src_upper, src_right, src_lower)), (dst_left, dst_upper))
+
+ header_file.write('#ifndef ' + font_name.upper() + '_H\n')
+ header_file.write('#define ' + font_name.upper() + '_H\n')
+ header_file.write('\n')
+ header_file.write('#include "bn_sprite_font.h"\n')
+ header_file.write('#include "bn_utf8_characters_map.h"\n')
+ header_file.write('#include "bn_sprite_items_' + fonts_file_name_no_ext + '.h"\n')
+ header_file.write('\n')
+ header_file.write('constexpr bn::utf8_character ' + font_name + '_utf8_characters[] = {\n')
+ header_file.write(' "' + '", "'.join(font_chars) + '"\n')
+ header_file.write('};\n')
+ header_file.write('\n')
+ header_file.write('constexpr bn::span ' + font_name + '_utf8_characters_span(\n')
+ header_file.write(' ' + font_name + '_utf8_characters);\n')
+ header_file.write('\n')
+ header_file.write('constexpr auto ' + font_name + '_utf8_characters_map =\n')
+ header_file.write(' bn::utf8_characters_map<' + font_name + '_utf8_characters_span>();\n')
+ header_file.write('\n')
+ header_file.write('constexpr int8_t ' + font_name + '_character_widths[] = {\n')
+ header_file.write(' ' + ', '.join([str(x) for x in font_widths]) + '\n')
+ header_file.write('};\n')
+ header_file.write('\n')
+ header_file.write('constexpr bn::sprite_font ' + font_name + '(\n')
+ header_file.write(' bn::sprite_items::' + fonts_file_name_no_ext + ',\n')
+ header_file.write(' ' + font_name + '_utf8_characters_map.reference(),\n')
+ header_file.write(' ' + font_name + '_character_widths);\n')
+ header_file.write('\n')
+ header_file.write('#endif')
+ fonts_image_path_no_ext = fonts_graphics_path + fonts_file_name_no_ext
+ font_number = len(font_widths) - 1
+ total_number += font_number
+ fonts_image_trimmed = Image.new('RGB', (font_width, font_height * total_number), transparent_color)
+ fonts_image_trimmed.paste(fonts_image)
+ #fonts_image_trimmed.save(fonts_image_path_no_ext + '.png')
+ fonts_image_trimmed = fonts_image_trimmed.convert("P", palette=Image.ADAPTIVE, colors=16)
+ transparent_color_index = fonts_image_trimmed.getpixel((0, 0))
+ if transparent_color_index > 0:
+ dest_map = list(range(16))
+ dest_map[0], dest_map[transparent_color_index] = transparent_color_index, 0
+ fonts_image_trimmed = fonts_image_trimmed.remap_palette(dest_map)
+ fonts_image_trimmed.save(fonts_image_path_no_ext + '.bmp')
+ with open(fonts_image_path_no_ext + '.json', 'w') as json_file:
+ json_file.write('{\n')
+ json_file.write(' "type": "sprite",\n')
+ json_file.write(' "height": ' + str(font_height) + '\n')
+ json_file.write('}\n')
+ print(' ' + fonts_file_path + ' font header written in ' + fonts_header_path + ' (character number: ' + str(font_number) + ')')
+
+ return total_number
+
+
+def process_fonts(fonts_folder_paths, build_folder_path, texts_paths):
+ texts_file_names, texts_file_paths = list_texts_files(texts_paths)
+ text_file_info_path = build_folder_path + '/_bn_texts_files_info.txt'
+ old_text_file_info = FileInfo.read(text_file_info_path)
+ new_text_file_info = FileInfo.build_from_files(texts_file_paths)
+
+ fonts_file_names, fonts_file_paths = list_fonts_files(fonts_folder_paths)
+ font_file_info_path = build_folder_path + '/_bn_fonts_files_info.txt'
+ old_font_file_info = FileInfo.read(font_file_info_path)
+ new_font_file_info = FileInfo.build_from_files(fonts_file_paths)
+
+ if old_font_file_info == new_font_file_info and old_text_file_info == new_text_file_info:
+ return
+
+ for fonts_file_name in fonts_file_names:
+ print(fonts_file_name)
+
+ sys.stdout.flush()
+
+ process_texts_files(texts_file_paths)
+ total_number = process_fonts_files(fonts_file_paths, build_folder_path)
+ print(' Processed character number: ' + str(total_number))
+ new_font_file_info.write(font_file_info_path)
+ new_text_file_info.write(text_file_info_path)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='Butano fonts tool.')
+ parser.add_argument('--build', required=True, help='build folder path')
+ parser.add_argument('--fonts', required=True, help='fonts folder paths')
+ parser.add_argument('--texts', required=False, help='texts folder or files paths')
+
+ try:
+ args = parser.parse_args()
+ process_fonts(args.fonts, args.build, args.texts)
+ except Exception as ex:
+ sys.stderr.write('Error: ' + str(ex) + '\n')
+ traceback.print_exc()
+ exit(-1)
diff --git a/credits/fonts.txt b/credits/fonts.txt
new file mode 100644
index 000000000..c4300ccea
--- /dev/null
+++ b/credits/fonts.txt
@@ -0,0 +1,5 @@
+- Source Han Sans
+ By Adobe, Google
+ Licensed under the SIL Open Font License v.1.1
+ https://commons.wikimedia.org/wiki/File:Source_Han_Sans_Version_2_Specimen.svg
+
diff --git a/docs/examples_font.png b/docs/examples_font.png
new file mode 100644
index 000000000..e03dc3ecc
Binary files /dev/null and b/docs/examples_font.png differ
diff --git a/examples/font/Makefile b/examples/font/Makefile
new file mode 100644
index 000000000..ffb7b08f6
--- /dev/null
+++ b/examples/font/Makefile
@@ -0,0 +1,59 @@
+#---------------------------------------------------------------------------------------------------------------------
+# TARGET is the name of the output.
+# BUILD is the directory where object files & intermediate files will be placed.
+# LIBBUTANO is the main directory of butano library (https://github.com/GValiente/butano).
+# PYTHON is the path to the python interpreter.
+# SOURCES is a list of directories containing source code.
+# INCLUDES is a list of directories containing extra header files.
+# DATA is a list of directories containing binary data.
+# GRAPHICS is a list of directories containing files to be processed by grit.
+# AUDIO is a list of directories containing files to be processed by mmutil.
+# FONTS is a list of directories containing font files.
+# ROMTITLE is a uppercase ASCII, max 12 characters text string containing the output ROM title.
+# ROMCODE is a uppercase ASCII, max 4 characters text string containing the output ROM code.
+# USERFLAGS is a list of additional compiler flags:
+# Pass -flto to enable link-time optimization.
+# Pass -O0 to improve debugging.
+# USERLIBDIRS is a list of additional directories containing libraries.
+# Each libraries directory must contains include and lib subdirectories.
+# USERLIBS is a list of additional libraries to link with the project.
+# USERBUILD is a list of additional directories to remove when cleaning the project.
+# EXTTOOL is an optional command executed before processing audio, graphics and code files.
+#
+# All directories are specified relative to the project directory where the makefile is found.
+#---------------------------------------------------------------------------------------------------------------------
+TARGET := $(notdir $(CURDIR))
+BUILD := build
+LIBBUTANO := ../../butano
+PYTHON := python
+SOURCES := src
+INCLUDES := include
+DATA :=
+GRAPHICS := graphics
+AUDIO := audio
+FONTS := fonts
+ROMTITLE := BUTANO FONT
+ROMCODE := SBTP
+USERFLAGS :=
+USERLIBDIRS :=
+USERLIBS :=
+USERBUILD :=
+EXTTOOL :=
+
+ifneq ($(strip $(FONTS)),)
+INCLUDES += $(BUILD)
+GRAPHICS += $(BUILD)/fonts
+EXTTOOL += $(PYTHON) $(LIBBUTANO)/tools/butano_fonts_tool.py --build=$(BUILD) --fonts="$(FONTS)"
+endif
+
+#---------------------------------------------------------------------------------------------------------------------
+# Export absolute butano path:
+#---------------------------------------------------------------------------------------------------------------------
+ifndef LIBBUTANOABS
+ export LIBBUTANOABS := $(realpath $(LIBBUTANO))
+endif
+
+#---------------------------------------------------------------------------------------------------------------------
+# Include main makefile:
+#---------------------------------------------------------------------------------------------------------------------
+include $(LIBBUTANOABS)/butano.mak
diff --git a/examples/font/audio/.gitignore b/examples/font/audio/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/font/fonts/user_variable_32x32.fnt b/examples/font/fonts/user_variable_32x32.fnt
new file mode 100644
index 000000000..1ffb458cb
--- /dev/null
+++ b/examples/font/fonts/user_variable_32x32.fnt
@@ -0,0 +1,40 @@
+info face="SourceHanSansSCVF-ExtraLight" size=30 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=1 aa=1 padding=1,1,1,1 spacing=1,1
+common lineHeight=30 base=30 scaleW=152 scaleH=149 pages=1 packed=0
+page id=0 file="user_variable_32x32.png"
+chars count=22
+char id=32 x=0 y=0 width=0 height=0 xoffset=0 yoffset=0 xadvance=7 page=0 chnl=15
+char id=12290 x=0 y=135 width=14 height=14 xoffset=-0.08800000000000008 yoffset=20.96 xadvance=30 page=0 chnl=15
+char id=12377 x=69 y=32 width=31 height=30 xoffset=1.024 yoffset=4.932 xadvance=30 page=0 chnl=15
+char id=12391 x=69 y=63 width=31 height=28 xoffset=0.9279999999999999 yoffset=7.018000000000001 xadvance=30 page=0 chnl=15
+char id=12398 x=101 y=0 width=30 height=27 xoffset=1.072 yoffset=7.042 xadvance=30 page=0 chnl=15
+char id=12457 x=101 y=117 width=26 height=25 xoffset=4 yoffset=9.994000000000002 xadvance=30 page=0 chnl=15
+char id=12470 x=35 y=102 width=32 height=31 xoffset=-0.05800000000000005 yoffset=4.938000000000001 xadvance=30 page=0 chnl=15
+char id=12488 x=132 y=0 width=20 height=30 xoffset=8.93 yoffset=5.058 xadvance=30 page=0 chnl=15
+char id=12501 x=101 y=90 width=26 height=26 xoffset=2.96 yoffset=7.914 xadvance=30 page=0 chnl=15
+char id=12518 x=69 y=92 width=31 height=23 xoffset=0.9220000000000002 yoffset=8.912 xadvance=30 page=0 chnl=15
+char id=12531 x=101 y=28 width=28 height=27 xoffset=3.002 yoffset=6.970000000000001 xadvance=30 page=0 chnl=15
+char id=12540 x=35 y=134 width=30 height=7 xoffset=1.072 yoffset=15.982 xadvance=30 page=0 chnl=15
+char id=20041 x=35 y=0 width=33 height=33 xoffset=-0.9359999999999999 yoffset=2.984 xadvance=30 page=0 chnl=15
+char id=20307 x=0 y=34 width=34 height=33 xoffset=-0.9180000000000001 yoffset=3.026 xadvance=30 page=0 chnl=15
+char id=23383 x=35 y=68 width=32 height=33 xoffset=0.04999999999999982 yoffset=2.9779999999999998 xadvance=30 page=0 chnl=15
+char id=23450 x=0 y=0 width=34 height=33 xoffset=-0.9180000000000001 yoffset=3.002 xadvance=30 page=0 chnl=15
+char id=25143 x=69 y=116 width=30 height=33 xoffset=-0.948 yoffset=2.9899999999999993 xadvance=30 page=0 chnl=15
+char id=26159 x=0 y=68 width=34 height=32 xoffset=-0.9119999999999999 yoffset=4.908 xadvance=30 page=0 chnl=15
+char id=29992 x=69 y=0 width=31 height=31 xoffset=-0.954 yoffset=5.058 xadvance=30 page=0 chnl=15
+char id=33258 x=101 y=56 width=26 height=33 xoffset=3.0919999999999996 yoffset=3.002 xadvance=30 page=0 chnl=15
+char id=35373 x=35 y=34 width=33 height=33 xoffset=-0.06400000000000006 yoffset=2.9959999999999996 xadvance=30 page=0 chnl=15
+char id=36825 x=0 y=101 width=33 height=33 xoffset=-0.10000000000000009 yoffset=2.9959999999999996 xadvance=30 page=0 chnl=15
+kernings count=13
+kerning first=12540 second=12470 amount=-1
+kerning first=12540 second=12501 amount=-2
+kerning first=12540 second=12531 amount=-1
+kerning first=12540 second=12391 amount=-2
+kerning first=12470 second=12290 amount=-2
+kerning first=12501 second=12290 amount=-1
+kerning first=12501 second=12531 amount=1
+kerning first=12457 second=12290 amount=-1
+kerning first=12531 second=12290 amount=-2
+kerning first=12531 second=12501 amount=-1
+kerning first=12488 second=12290 amount=-1
+kerning first=12391 second=12290 amount=-1
+kerning first=12377 second=12290 amount=-3
diff --git a/examples/font/fonts/user_variable_32x32.png b/examples/font/fonts/user_variable_32x32.png
new file mode 100644
index 000000000..02694634a
Binary files /dev/null and b/examples/font/fonts/user_variable_32x32.png differ
diff --git a/examples/font/graphics/.gitignore b/examples/font/graphics/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/font/include/.gitignore b/examples/font/include/.gitignore
new file mode 100644
index 000000000..e69de29bb
diff --git a/examples/font/src/main.cpp b/examples/font/src/main.cpp
new file mode 100644
index 000000000..219c8414f
--- /dev/null
+++ b/examples/font/src/main.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020-2022 Gustavo Valiente gustavo.valiente@protonmail.com
+ * zlib License, see LICENSE file.
+ */
+
+#include "bn_core.h"
+#include "bn_math.h"
+#include "bn_keypad.h"
+#include "bn_sprite_ptr.h"
+#include "bn_bg_palettes.h"
+#include "bn_string_view.h"
+#include "bn_sprite_text_generator.h"
+
+#include "user_variable_32x32_sprite_font.h"
+
+namespace
+{
+ void user_font_text_scene()
+ {
+ bn::sprite_text_generator user_font_text_generator(user_variable_32x32_sprite_font);
+ user_font_text_generator.set_center_alignment();
+
+ bn::vector user_font_text_sprites;
+ user_font_text_generator.generate(0, -48, "这是用户自定义", user_font_text_sprites);
+ user_font_text_generator.generate(0, -16, "字体。", user_font_text_sprites);
+ user_font_text_generator.generate(0, 16, "ユーザー設定の", user_font_text_sprites);
+ user_font_text_generator.generate(0, 48, "フォントです。", user_font_text_sprites);
+
+ while(! bn::keypad::start_pressed())
+ {
+ bn::core::update();
+ }
+ }
+}
+
+int main()
+{
+ bn::core::init();
+
+ bn::bg_palettes::set_transparent_color(bn::color(16, 16, 16));
+
+ while(true)
+ {
+ user_font_text_scene();
+ bn::core::update();
+ }
+}
| ||