mirror of
https://github.com/AntonioND/nitro-engine.git
synced 2025-06-18 16:45:33 -04:00
library: Add rich text support
This support relies on BMFont: https://www.angelcode.com/products/bmfont/ Nitro Engine doesn't support that format directly. Instead, it uses LibDSF internally: https://github.com/AntonioND/libdsf
This commit is contained in:
parent
951f0f87ca
commit
50e14b2c21
4
Makefile
4
Makefile
@ -15,9 +15,9 @@ include $(DEVKITARM)/ds_rules
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
#---------------------------------------------------------------------------------
|
||||
SOURCES := source source/dsma
|
||||
SOURCES := source source/dsma source/libdsf
|
||||
DATA := data
|
||||
INCLUDES := include
|
||||
INCLUDES := include source/libdsf
|
||||
|
||||
ifeq ($(NE_DEBUG),1)
|
||||
TARGET := NE_debug
|
||||
|
@ -12,7 +12,7 @@ ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/
|
||||
# -----------------
|
||||
|
||||
SOURCEDIRS := source
|
||||
INCLUDEDIRS := include
|
||||
INCLUDEDIRS := include source/libdsf
|
||||
GFXDIRS :=
|
||||
BINDIRS :=
|
||||
|
||||
|
@ -37,6 +37,7 @@ extern "C" {
|
||||
#include "NEPalette.h"
|
||||
#include "NEPhysics.h"
|
||||
#include "NEPolygon.h"
|
||||
#include "NERichText.h"
|
||||
#include "NEText.h"
|
||||
#include "NETexture.h"
|
||||
|
||||
|
168
include/NERichText.h
Normal file
168
include/NERichText.h
Normal file
@ -0,0 +1,168 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright (c) 2024 Antonio Niño Díaz
|
||||
//
|
||||
// This file is part of Nitro Engine
|
||||
|
||||
#ifndef NE_RICHTEXT_H__
|
||||
#define NE_RICHTEXT_H__
|
||||
|
||||
/// @file NERichText.h
|
||||
/// @brief Rich text system.
|
||||
|
||||
/// @defgroup text_system Text system
|
||||
///
|
||||
/// Rich text drawing functions. It is possible to draw text as a series of
|
||||
/// quad, or to render text to a material (which needs to be drawn by the
|
||||
/// user as a single quad).
|
||||
///
|
||||
/// The functions accept '\n', but not things like '%d', '%s', etc. You'll need
|
||||
/// to use snprintf() or similar for that.
|
||||
///
|
||||
/// You need to call NE_2DViewInit() before using any of the quad drawing
|
||||
/// functions.
|
||||
///
|
||||
/// In order to generate fonts, please check the website of BMFont:
|
||||
///
|
||||
/// https://www.angelcode.com/products/bmfont/
|
||||
///
|
||||
/// You need to export fonts in binary format, and the images in PNG format.
|
||||
///
|
||||
/// @{
|
||||
|
||||
#define NE_MAX_RICH_TEXT_FONTS 8 ///< Default max number of rich text fonts
|
||||
|
||||
/// Change the priority of rich text drawn after this function call.
|
||||
///
|
||||
/// @param priority New priority.
|
||||
void NE_RichTextPrioritySet(int priority);
|
||||
|
||||
/// Set to 0 the priority of rich text drawn after this function call.
|
||||
void NE_RichTextPriorityReset(void);
|
||||
|
||||
/// Initialize a rich text slot.
|
||||
///
|
||||
/// @param slot The slot to initialize (from 0 to NE_MAX_RICH_TEXT_FONTS).
|
||||
void NE_RichTextInit(u32 slot);
|
||||
|
||||
/// End a rich text slot and free all the resources used by it.
|
||||
///
|
||||
/// @param slot The slot to end.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextEnd(u32 slot);
|
||||
|
||||
/// Load font metadata from the specified file in the filesystem.
|
||||
///
|
||||
/// @param slot The slot to use.
|
||||
/// @param path Path to a '.fnt' binary file.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextMetadataLoadFAT(u32 slot, const char *path);
|
||||
|
||||
/// Load font metadata from the buffer in RAM.
|
||||
///
|
||||
/// @param slot The slot to use.
|
||||
/// @param data Pointer to a '.fnt' binary file in RAM
|
||||
/// @param data_size Size of the file.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextMetadataLoadMemory(u32 slot, const void *data, size_t data_size);
|
||||
|
||||
/// Load a GRF file to be used as material when drawing text as 3D quads.
|
||||
///
|
||||
/// This loads a GRF file with a texture and loads it to VRAM as a material.
|
||||
///
|
||||
/// This is required for NE_RichTextRender3D() and NE_RichTextRender3DAlpha().
|
||||
///
|
||||
/// This isn't required for NE_RichTextRenderMaterial().
|
||||
///
|
||||
/// @param slot The slot to use.
|
||||
/// @param path Path to the GRF file.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextMaterialLoadGRF(u32 slot, const char *path);
|
||||
|
||||
/// Assign a material to the slot, which will be used to draw text as 3D quads.
|
||||
///
|
||||
/// After this call, the font will own the material and texture, and it will
|
||||
/// delete them when the font is deleted. The caller of this function must not
|
||||
/// try to delete the material or palette manually after calling this function.
|
||||
///
|
||||
/// This is required for NE_RichTextRender3D() and NE_RichTextRender3DAlpha().
|
||||
///
|
||||
/// This isn't required for NE_RichTextRenderMaterial().
|
||||
///
|
||||
/// @param slot The slot to use.
|
||||
/// @param mat The material to assign.
|
||||
/// @param pal The palette to assign.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextMaterialSet(u32 slot, NE_Material *mat, NE_Palette *pal);
|
||||
|
||||
/// Load a GRF file to RAM to be used to render text to textures.
|
||||
///
|
||||
/// This doesn't load the texture to VRAM, it keeps it in RAM. The texture will
|
||||
/// be used whenever the user uses NE_RichTextRenderMaterial() to render text to
|
||||
/// a new material.
|
||||
///
|
||||
/// This is required for NE_RichTextRenderMaterial().
|
||||
///
|
||||
/// This isn't required for NE_RichTextRender3D() or NE_RichTextRender3DAlpha().
|
||||
///
|
||||
/// @param slot The slot to use.
|
||||
/// @param path Path to the GRF file.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextBitmapLoadGRF(u32 slot, const char *path);
|
||||
|
||||
/// Render a string by rendering one 3D quad per codepoint.
|
||||
///
|
||||
/// This preserves the polygon format that is currently active.
|
||||
///
|
||||
/// @param slot The slot to use.
|
||||
/// @param str The string to render.
|
||||
/// @param x The left coordinate of the text.
|
||||
/// @param y The top coordinate of the text.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextRender3D(u32 slot, const char *str, s32 x, s32 y);
|
||||
|
||||
/// Render a string by rendering one 3D quad per codepoint with alternating
|
||||
/// polygon IDs.
|
||||
///
|
||||
/// This function will alternate between polygon IDs so that alpha blending
|
||||
/// works between multiple polygons when they overlap. This is a requirement of
|
||||
/// the NDS 3D hardware.
|
||||
///
|
||||
/// It is required to pass the base polygon format as a parameter because the
|
||||
/// polygon format data is write-only. Whenever the polygon ID needs to be
|
||||
/// changed, the rest of the polygon format flags need to be set as well.
|
||||
///
|
||||
/// @param slot The slot to use.
|
||||
/// @param str The string to render.
|
||||
/// @param x The left coordinate of the text.
|
||||
/// @param y The top coordinate of the text.
|
||||
/// @param poly_fmt The polygon format values to be used for the quads.
|
||||
/// @param poly_id_base The base polygon ID to use for the quads.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextRender3DAlpha(u32 slot, const char *str, s32 x, s32 y,
|
||||
uint32_t poly_fmt, int poly_id_base);
|
||||
|
||||
/// Render a string with the specified font and create a material from it.
|
||||
///
|
||||
/// This function renders the string to a buffer and creates a material that
|
||||
/// uses that buffer as texture. This is quite CPU-intensive, but it makes sense
|
||||
/// to do it if the text is going to be displayed unchanged for a long time.
|
||||
///
|
||||
/// If the font uses an image that doesn't use palette, it will return a
|
||||
/// material and it won't try to return a palette. If the font uses an image
|
||||
/// with a palette, there are two options. If you want the pointer to the
|
||||
/// palette, the function will give it to you. If you don't want the pointer,
|
||||
/// you can pass NULL to that argument, and the palette will be autodeleted when
|
||||
/// the material is deleted.
|
||||
///
|
||||
/// @param slot The font slot to use.
|
||||
/// @param str The string to print.
|
||||
/// @param mat A pointer to a NE_Material to store the new material.
|
||||
/// @param pal A pointer to a NE_Palette to store the new palette, or NULL.
|
||||
/// @return Returns 1 on success, 0 on failure.
|
||||
int NE_RichTextRenderMaterial(u32 slot, const char *str, NE_Material **mat,
|
||||
NE_Palette **pal);
|
||||
|
||||
/// @}
|
||||
|
||||
#endif // NE_RICHTEXT_H__
|
@ -17,7 +17,7 @@
|
||||
/// for that.
|
||||
///
|
||||
/// You need to call NE_2DViewInit() before using any of the text drawing
|
||||
/// functions..
|
||||
/// functions.
|
||||
///
|
||||
/// @{
|
||||
|
||||
|
383
source/NERichText.c
Normal file
383
source/NERichText.c
Normal file
@ -0,0 +1,383 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Copyright (c) 2024 Antonio Niño Díaz
|
||||
//
|
||||
// This file is part of Nitro Engine
|
||||
|
||||
#include "NEMain.h"
|
||||
|
||||
#include "libdsf/dsf.h"
|
||||
|
||||
/// @file NERichText.c
|
||||
|
||||
typedef struct {
|
||||
// Fields used when the font texture is stored in VRAM
|
||||
NE_Material *material;
|
||||
NE_Palette *palette;
|
||||
|
||||
// Fields used when the font texture is stored in RAM
|
||||
NE_TextureFormat fmt;
|
||||
void *texture_buffer;
|
||||
size_t texture_width;
|
||||
size_t texture_height;
|
||||
void *palette_buffer;
|
||||
size_t palette_size;
|
||||
|
||||
// Other fields
|
||||
dsf_handle handle;
|
||||
bool active;
|
||||
} ne_rich_textinfo_t;
|
||||
|
||||
static ne_rich_textinfo_t NE_RichTextInfo[NE_MAX_RICH_TEXT_FONTS];
|
||||
|
||||
static int NE_RICH_TEXT_PRIORITY = 0;
|
||||
|
||||
void NE_RichTextInit(u32 slot)
|
||||
{
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (info->active)
|
||||
NE_RichTextEnd(slot);
|
||||
|
||||
memset(info, 0, sizeof(ne_rich_textinfo_t));
|
||||
info->active = true;
|
||||
}
|
||||
|
||||
int NE_RichTextEnd(u32 slot)
|
||||
{
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
if (info->material != NULL)
|
||||
NE_MaterialDelete(info->material);
|
||||
if (info->palette != NULL)
|
||||
NE_PaletteDelete(info->palette);
|
||||
|
||||
if (info->texture_buffer != NULL)
|
||||
free(info->texture_buffer);
|
||||
if (info->palette_buffer != NULL)
|
||||
free(info->palette_buffer);
|
||||
|
||||
int ret = 0;
|
||||
|
||||
if (info->handle)
|
||||
{
|
||||
dsf_error err = DSF_FreeFont(&(info->handle));
|
||||
if (err != DSF_NO_ERROR)
|
||||
ret = err;
|
||||
}
|
||||
|
||||
memset(info, 0, sizeof(ne_rich_textinfo_t));
|
||||
|
||||
if (ret != 0)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NE_RichTextMetadataLoadFAT(u32 slot, const char *path)
|
||||
{
|
||||
NE_AssertPointer(path, "NULL path pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
dsf_handle handle;
|
||||
dsf_error ret = DSF_LoadFontFilesystem(&handle, path);
|
||||
if (ret != DSF_NO_ERROR)
|
||||
{
|
||||
NE_DebugPrint("DSF_LoadFontFilesystem(): %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
info->handle = handle;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NE_RichTextMetadataLoadMemory(u32 slot, const void *data, size_t data_size)
|
||||
{
|
||||
NE_AssertPointer(data, "NULL data pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
dsf_handle handle;
|
||||
dsf_error ret = DSF_LoadFontMemory(&handle, data, data_size);
|
||||
if (ret != DSF_NO_ERROR)
|
||||
{
|
||||
NE_DebugPrint("DSF_LoadFontMemory(): %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
info->handle = handle;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NE_RichTextMaterialLoadGRF(u32 slot, const char *path)
|
||||
{
|
||||
NE_AssertPointer(path, "NULL path pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
info->material = NE_MaterialCreate();
|
||||
info->palette = NE_PaletteCreate();
|
||||
|
||||
int ret = NE_MaterialTexLoadGRF(info->material, info->palette,
|
||||
NE_TEXGEN_TEXCOORD | NE_TEXTURE_COLOR0_TRANSPARENT, path);
|
||||
if (ret == 0)
|
||||
{
|
||||
NE_MaterialDelete(info->material);
|
||||
NE_PaletteDelete(info->palette);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NE_RichTextMaterialSet(u32 slot, NE_Material *mat, NE_Palette *pal)
|
||||
{
|
||||
NE_AssertPointer(mat, "NULL material pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
info->material = mat;
|
||||
info->palette = pal;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NE_RichTextBitmapLoadGRF(u32 slot, const char *path)
|
||||
{
|
||||
#ifndef NE_BLOCKSDS
|
||||
(void)tex;
|
||||
(void)pal;
|
||||
(void)flags;
|
||||
(void)path;
|
||||
NE_DebugPrint("%s only supported in BlocksDS", __func__);
|
||||
return 0;
|
||||
#else // NE_BLOCKSDS
|
||||
NE_AssertPointer(path, "NULL path pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
if (info->texture_buffer != NULL)
|
||||
free(info->texture_buffer);
|
||||
if (info->palette_buffer != NULL)
|
||||
free(info->palette_buffer);
|
||||
|
||||
int ret = 0;
|
||||
|
||||
void *gfxDst = NULL;
|
||||
void *palDst = NULL;
|
||||
size_t palSize;
|
||||
GRFHeader header = { 0 };
|
||||
GRFError err = grfLoadPath(path, &header, &gfxDst, NULL, NULL, NULL,
|
||||
&palDst, &palSize);
|
||||
if (err != GRF_NO_ERROR)
|
||||
{
|
||||
NE_DebugPrint("Couldn't load GRF file: %d", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (gfxDst == NULL)
|
||||
{
|
||||
NE_DebugPrint("No graphics found in GRF file");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
bool palette_required = true;
|
||||
switch (header.gfxAttr)
|
||||
{
|
||||
case GRF_TEXFMT_A5I3:
|
||||
info->fmt = NE_A5PAL8;
|
||||
break;
|
||||
case GRF_TEXFMT_A3I5:
|
||||
info->fmt = NE_A3PAL32;
|
||||
break;
|
||||
case GRF_TEXFMT_4x4:
|
||||
info->fmt = NE_TEX4X4;
|
||||
break;
|
||||
case 16:
|
||||
info->fmt = NE_A1RGB5;
|
||||
palette_required = false;
|
||||
break;
|
||||
case 8:
|
||||
info->fmt = NE_PAL256;
|
||||
break;
|
||||
case 4:
|
||||
info->fmt = NE_PAL16;
|
||||
break;
|
||||
case 2:
|
||||
info->fmt = NE_PAL4;
|
||||
break;
|
||||
default:
|
||||
NE_DebugPrint("Invalid format in GRF file");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
info->texture_buffer = gfxDst;
|
||||
info->texture_width = header.gfxWidth;
|
||||
info->texture_height = header.gfxHeight;
|
||||
|
||||
if (palDst != NULL)
|
||||
{
|
||||
info->palette_buffer = palDst;
|
||||
info->palette_size = palSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (palette_required)
|
||||
{
|
||||
NE_DebugPrint("No palette found in GRF, but format requires it");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
info->palette_buffer = NULL;
|
||||
info->palette_size = 0;
|
||||
}
|
||||
|
||||
ret = 1; // Success
|
||||
|
||||
cleanup:
|
||||
free(gfxDst);
|
||||
free(palDst);
|
||||
return ret;
|
||||
#endif // NE_BLOCKSDS
|
||||
}
|
||||
|
||||
int NE_RichTextRender3D(u32 slot, const char *str, s32 x, s32 y)
|
||||
{
|
||||
NE_AssertPointer(str, "NULL str pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
NE_MaterialUse(info->material);
|
||||
|
||||
dsf_error err = DSF_StringRender3D(info->handle, str, x, y,
|
||||
NE_RICH_TEXT_PRIORITY);
|
||||
if (err != DSF_NO_ERROR)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NE_RichTextRender3DAlpha(u32 slot, const char *str, s32 x, s32 y,
|
||||
uint32_t poly_fmt, int poly_id_base)
|
||||
{
|
||||
NE_AssertPointer(str, "NULL str pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
NE_MaterialUse(info->material);
|
||||
|
||||
dsf_error err = DSF_StringRender3DAlpha(info->handle, str, x, y,
|
||||
NE_RICH_TEXT_PRIORITY,
|
||||
poly_fmt, poly_id_base);
|
||||
if (err != DSF_NO_ERROR)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int NE_RichTextRenderMaterial(u32 slot, const char *str, NE_Material **mat,
|
||||
NE_Palette **pal)
|
||||
{
|
||||
NE_AssertPointer(str, "NULL str pointer");
|
||||
NE_AssertPointer(mat, "NULL mat pointer");
|
||||
NE_AssertPointer(pal, "NULL pal pointer");
|
||||
|
||||
if (slot >= NE_MAX_RICH_TEXT_FONTS)
|
||||
return 0;
|
||||
|
||||
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
||||
if (!info->active)
|
||||
return 0;
|
||||
|
||||
void *out_texture;
|
||||
size_t out_width, out_height;
|
||||
dsf_error err = DSF_StringRenderToTexture(info->handle,
|
||||
str, info->fmt, info->texture_buffer,
|
||||
info->texture_width, info->texture_height,
|
||||
&out_texture, &out_width, &out_height);
|
||||
if (err != DSF_NO_ERROR)
|
||||
{
|
||||
free(out_texture);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*mat = NE_MaterialCreate();
|
||||
if (NE_MaterialTexLoad(*mat, info->fmt, out_width, out_height,
|
||||
NE_TEXGEN_TEXCOORD | NE_TEXTURE_COLOR0_TRANSPARENT,
|
||||
out_texture) == 0)
|
||||
{
|
||||
free(out_texture);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (info->palette_buffer != NULL)
|
||||
{
|
||||
NE_Palette *palette = NE_PaletteCreate();
|
||||
if (NE_PaletteLoad(palette, info->palette_buffer,
|
||||
info->palette_size / 2, info->fmt) == 0)
|
||||
{
|
||||
NE_MaterialDelete(*mat);
|
||||
free(out_texture);
|
||||
return 0;
|
||||
}
|
||||
|
||||
NE_MaterialSetPalette(*mat, palette);
|
||||
|
||||
// If the caller has requested the pointer to the palette, return the
|
||||
// value to the user. If not, mark it to be autodeleted.
|
||||
if (pal)
|
||||
*pal = palette;
|
||||
else
|
||||
NE_MaterialAutodeletePalette(*mat);
|
||||
}
|
||||
|
||||
// This isn't needed after it has been loaded to VRAM
|
||||
free(out_texture);
|
||||
|
||||
return 1;
|
||||
}
|
949
source/libdsf/dsf.c
Normal file
949
source/libdsf/dsf.c
Normal file
@ -0,0 +1,949 @@
|
||||
// SPDX-License-Identifier: Zlib
|
||||
//
|
||||
// Copyright (c) 2024 Antonio Niño Díaz
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <nds.h>
|
||||
|
||||
#include <dsf.h>
|
||||
|
||||
// BMFont format structures
|
||||
// ------------------------
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t magic[3]; // "BMF"
|
||||
uint8_t version; // 3
|
||||
} bmf_header;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint8_t type; // 1, 2, 3, 4 or 5
|
||||
uint8_t size[4]; // Size of the block
|
||||
uint8_t data[];
|
||||
} bmf_block_header;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint16_t line_height; // Distance in pixels between each line of text.
|
||||
uint16_t base; // Number of pixels from the top of the line to the base of the characters.
|
||||
uint16_t scale_w;
|
||||
uint16_t scale_h;
|
||||
uint16_t pages;
|
||||
uint8_t bit_field;
|
||||
uint8_t alpha_channel;
|
||||
uint8_t red_channel;
|
||||
uint8_t green_channel;
|
||||
uint8_t blue_channel;
|
||||
} bmf_block_2_common;
|
||||
|
||||
// The number of characters in one file can be calculated by calculating the
|
||||
// result of "size / sizeof(bmf_block_4_char)"
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t id;
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
int16_t xoffset;
|
||||
int16_t yoffset;
|
||||
int16_t xadvance;
|
||||
uint8_t page;
|
||||
uint8_t channel;
|
||||
} bmf_block_4_char;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
uint32_t first;
|
||||
uint32_t second;
|
||||
uint16_t amount;
|
||||
} bmf_block_5_kerning_pair;
|
||||
|
||||
// Internal data structures
|
||||
// ------------------------
|
||||
|
||||
typedef bmf_block_4_char block_char;
|
||||
|
||||
typedef struct {
|
||||
uint32_t first;
|
||||
uint32_t second;
|
||||
int16_t amount;
|
||||
} kerning_pair;
|
||||
|
||||
typedef struct {
|
||||
uint16_t line_height;
|
||||
uint16_t base;
|
||||
|
||||
size_t num_chars;
|
||||
block_char *chars;
|
||||
size_t num_kernings;
|
||||
kerning_pair *kernings;
|
||||
|
||||
// Variables used for the current printing context
|
||||
int16_t pointer_x;
|
||||
int16_t pointer_y;
|
||||
int16_t box_left;
|
||||
int16_t box_top;
|
||||
uint32_t last_codepoint;
|
||||
|
||||
// If the font includes a replacement character glyph, its index will be
|
||||
// saved here for ease of access.
|
||||
int32_t replacement_character_id;
|
||||
} dsf_font_internal_state;
|
||||
|
||||
// Functions
|
||||
// ---------
|
||||
|
||||
static int DSF_CodepointFindGlyphIndex(dsf_handle handle, uint32_t codepoint)
|
||||
{
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
for (size_t i = 0; i < font->num_chars; i++)
|
||||
{
|
||||
block_char *ch = &(font->chars[i]);
|
||||
|
||||
if (ch->id == codepoint)
|
||||
return i;
|
||||
}
|
||||
|
||||
return font->replacement_character_id;
|
||||
}
|
||||
|
||||
static dsf_error DSF_LoadFile(const char *path, void **data, size_t *_size)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (f == NULL)
|
||||
return DSF_FILE_OPEN_ERROR;
|
||||
|
||||
if (fseek(f, 0, SEEK_END) != 0)
|
||||
{
|
||||
fclose(f);
|
||||
return DSF_FILE_SEEK_ERROR;
|
||||
}
|
||||
|
||||
size_t size = ftell(f);
|
||||
if (size == 0)
|
||||
{
|
||||
fclose(f);
|
||||
return DSF_FILE_EMPTY;
|
||||
}
|
||||
|
||||
rewind(f);
|
||||
|
||||
char *buffer = malloc(size);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
fclose(f);
|
||||
return DSF_NO_MEMORY;
|
||||
}
|
||||
|
||||
if (fread(buffer, 1, size, f) != size)
|
||||
{
|
||||
fclose(f);
|
||||
return DSF_FILE_READ_ERROR;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
*_size = size;
|
||||
*data = buffer;
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
dsf_error DSF_LoadFontMemory(dsf_handle *handle,
|
||||
const void *data, int32_t data_size)
|
||||
{
|
||||
dsf_error ret = DSF_NO_ERROR;
|
||||
const uint8_t *ptr = data;
|
||||
|
||||
// Basic checks
|
||||
|
||||
if ((handle == NULL) || (data == NULL) || (data_size <= 0))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
// Read header
|
||||
|
||||
const bmf_header *header = (const bmf_header *)ptr;
|
||||
|
||||
if (!((header->magic[0] == 'B') && (header->magic[1] == 'M') &&
|
||||
(header->magic[2] == 'F')))
|
||||
return DSF_BAD_MAGIC;
|
||||
|
||||
if (header->version != 3)
|
||||
return DSF_BAD_VERSION;
|
||||
|
||||
ptr += sizeof(bmf_header);
|
||||
data_size -= sizeof(bmf_header);
|
||||
|
||||
// Allocate space for the handle
|
||||
|
||||
dsf_font_internal_state *font = calloc(1, sizeof(dsf_font_internal_state));
|
||||
if (font == NULL)
|
||||
return DSF_NO_MEMORY;
|
||||
|
||||
*handle = (dsf_handle)font; // Return handle to user
|
||||
|
||||
// Read blocks
|
||||
|
||||
while (1)
|
||||
{
|
||||
const bmf_block_header *block_header = (const bmf_block_header *)ptr;
|
||||
|
||||
uint8_t type = block_header->type;
|
||||
uint32_t block_size = ((uint32_t)block_header->size[0]) |
|
||||
((uint32_t)block_header->size[1] << 8) |
|
||||
((uint32_t)block_header->size[2] << 16) |
|
||||
((uint32_t)block_header->size[3] << 24);
|
||||
|
||||
const uint8_t *data = ptr + 1 + 4;
|
||||
|
||||
if (type == 2)
|
||||
{
|
||||
// Ensure the size is correct
|
||||
if (block_size != sizeof(bmf_block_2_common))
|
||||
{
|
||||
ret = DSF_BAD_CHUNK_SIZE;
|
||||
goto error;
|
||||
}
|
||||
|
||||
bmf_block_2_common block_common;
|
||||
memcpy(&block_common, data, block_size);
|
||||
|
||||
font->line_height = block_common.line_height;
|
||||
font->base = block_common.base;
|
||||
}
|
||||
else if (type == 4)
|
||||
{
|
||||
// Ensure the total size is a multiple of the block size
|
||||
if (block_size % sizeof(bmf_block_4_char) != 0)
|
||||
{
|
||||
ret = DSF_BAD_CHUNK_SIZE;
|
||||
goto error;
|
||||
}
|
||||
|
||||
font->num_chars = block_size / sizeof(bmf_block_4_char);
|
||||
font->chars = calloc(font->num_chars, sizeof(block_char));
|
||||
if (font->chars == NULL)
|
||||
{
|
||||
ret = DSF_NO_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
|
||||
memcpy(font->chars, data, block_size);
|
||||
}
|
||||
else if (type == 5)
|
||||
{
|
||||
// Ensure the total size is a multiple of the block size
|
||||
if (block_size % sizeof(bmf_block_5_kerning_pair) != 0)
|
||||
{
|
||||
ret = DSF_BAD_CHUNK_SIZE;
|
||||
goto error;
|
||||
}
|
||||
|
||||
font->num_kernings = block_size / sizeof(bmf_block_5_kerning_pair);
|
||||
font->kernings = calloc(font->num_kernings, sizeof(kerning_pair));
|
||||
if (font->kernings == NULL)
|
||||
{
|
||||
ret = DSF_NO_MEMORY;
|
||||
goto error;
|
||||
}
|
||||
|
||||
const uint8_t *src = data;
|
||||
uint8_t *dst = (uint8_t *)font->kernings;
|
||||
|
||||
for (size_t i = 0; i < font->num_kernings; i++)
|
||||
{
|
||||
memcpy(dst, src, sizeof(bmf_block_5_kerning_pair));
|
||||
dst += sizeof(kerning_pair);
|
||||
src += sizeof(bmf_block_5_kerning_pair);
|
||||
}
|
||||
}
|
||||
|
||||
ptr += block_size + 1 + 4;
|
||||
data_size -= block_size + 1 + 4;
|
||||
|
||||
if (data_size == 0)
|
||||
break;
|
||||
|
||||
if (data_size < 0)
|
||||
{
|
||||
ret = DSF_UNEXPECTED_END;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (font->num_chars == 0)
|
||||
{
|
||||
ret = DSF_NO_CHARACTERS;
|
||||
goto error;
|
||||
}
|
||||
|
||||
// Look for a replacement character glyph in the font.
|
||||
|
||||
// Initialize the value to -1 so that DSF_CodepointFindGlyphIndex() returns
|
||||
// -1 if no replacement character glyph is found.
|
||||
font->replacement_character_id = -1;
|
||||
font->replacement_character_id =
|
||||
DSF_CodepointFindGlyphIndex(*handle, REPLACEMENT_CHARACTER);
|
||||
|
||||
return DSF_NO_ERROR;
|
||||
|
||||
error:
|
||||
free(font->chars);
|
||||
free(font->kernings);
|
||||
free(font);
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsf_error DSF_FreeFont(dsf_handle *handle)
|
||||
{
|
||||
if (handle == NULL)
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)*handle;
|
||||
|
||||
free(font->chars);
|
||||
free(font->kernings);
|
||||
free(font);
|
||||
|
||||
*handle = 0;
|
||||
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
dsf_error DSF_LoadFontFilesystem(dsf_handle *handle, const char *path)
|
||||
{
|
||||
if ((handle == NULL) || (path == NULL))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
size_t size;
|
||||
void *data;
|
||||
dsf_error error = DSF_LoadFile(path, &data, &size);
|
||||
if (error != DSF_NO_ERROR)
|
||||
return error;
|
||||
|
||||
error = DSF_LoadFontMemory(handle, data, size);
|
||||
free(data);
|
||||
return error;
|
||||
}
|
||||
|
||||
static size_t DSF_UTF8_CodepointRead(const char *str, uint32_t *codepoint)
|
||||
{
|
||||
// https://en.wikipedia.org/wiki/UTF-8#Encoding
|
||||
|
||||
size_t size;
|
||||
uint32_t rune;
|
||||
|
||||
uint32_t b1 = str[0];
|
||||
|
||||
if ((b1 & 0x80) == 0)
|
||||
{
|
||||
size = 1;
|
||||
*codepoint = b1 & 0x7F;
|
||||
return 1;
|
||||
}
|
||||
else if ((b1 & 0xE0) == 0xC0)
|
||||
{
|
||||
size = 2;
|
||||
rune = b1 & 0x1F;
|
||||
}
|
||||
else if ((b1 & 0xF0) == 0xE0)
|
||||
{
|
||||
size = 3;
|
||||
rune = b1 & 0x0F;
|
||||
}
|
||||
else if ((b1 & 0xF8) == 0xF0)
|
||||
{
|
||||
size = 4;
|
||||
rune = b1 & 0x07;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < size; i++)
|
||||
{
|
||||
uint32_t b = str[i];
|
||||
if ((b & 0xC0) != 0x80)
|
||||
goto error;
|
||||
|
||||
rune <<= 6;
|
||||
rune |= b & 0x3F;
|
||||
}
|
||||
|
||||
*codepoint = rune;
|
||||
return size;
|
||||
|
||||
error:
|
||||
|
||||
// Incorrect encoding. Advance characters until we find one character
|
||||
// that isn't a continuation character.
|
||||
size = 1;
|
||||
str++;
|
||||
|
||||
while (1)
|
||||
{
|
||||
uint8_t c = *str++;
|
||||
size++;
|
||||
if ((c & 0xC0) != 0x80)
|
||||
break;
|
||||
}
|
||||
|
||||
*codepoint = REPLACEMENT_CHARACTER;
|
||||
return size;
|
||||
}
|
||||
|
||||
dsf_error DSF_CodepointRenderDryRun(dsf_handle handle, uint32_t codepoint)
|
||||
{
|
||||
if ((handle == 0) || (codepoint == 0))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
font->pointer_x = font->box_left;
|
||||
font->pointer_y += font->line_height;
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
int index = DSF_CodepointFindGlyphIndex(handle, codepoint);
|
||||
if (index < 0)
|
||||
return DSF_CODEPOINT_NOT_FOUND;
|
||||
|
||||
block_char *ch = &(font->chars[index]);
|
||||
|
||||
int x1 = font->pointer_x;
|
||||
int x2 = x1 + ch->width;
|
||||
int y1 = font->pointer_y;
|
||||
int y2 = y1 + ch->height;
|
||||
|
||||
x1 += ch->xoffset;
|
||||
x2 += ch->xoffset;
|
||||
y1 += ch->yoffset;
|
||||
y2 += ch->yoffset;
|
||||
|
||||
font->pointer_x += ch->xadvance;
|
||||
|
||||
for (size_t i = 0; i < font->num_kernings; i++)
|
||||
{
|
||||
kerning_pair *ker = &(font->kernings[i]);
|
||||
|
||||
if (ker->first != font->last_codepoint)
|
||||
continue;
|
||||
|
||||
if (ker->second != codepoint)
|
||||
continue;
|
||||
|
||||
x1 += ker->amount;
|
||||
x2 += ker->amount;
|
||||
font->pointer_x += ker->amount;
|
||||
}
|
||||
|
||||
font->last_codepoint = codepoint;
|
||||
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
static dsf_error DSF_CodepointRender3D(dsf_handle handle, uint32_t codepoint,
|
||||
int16_t z)
|
||||
{
|
||||
if ((handle == 0) || (codepoint == 0))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
font->pointer_x = font->box_left;
|
||||
font->pointer_y += font->line_height;
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
int index = DSF_CodepointFindGlyphIndex(handle, codepoint);
|
||||
if (index < 0)
|
||||
return DSF_CODEPOINT_NOT_FOUND;
|
||||
|
||||
block_char *ch = &(font->chars[index]);
|
||||
|
||||
int tx1 = ch->x;
|
||||
int tx2 = tx1 + ch->width;
|
||||
int ty1 = ch->y;
|
||||
int ty2 = ty1 + ch->height;
|
||||
|
||||
int x1 = font->pointer_x;
|
||||
int x2 = x1 + ch->width;
|
||||
int y1 = font->pointer_y;
|
||||
int y2 = y1 + ch->height;
|
||||
|
||||
x1 += ch->xoffset;
|
||||
x2 += ch->xoffset;
|
||||
y1 += ch->yoffset;
|
||||
y2 += ch->yoffset;
|
||||
|
||||
font->pointer_x += ch->xadvance;
|
||||
|
||||
for (size_t i = 0; i < font->num_kernings; i++)
|
||||
{
|
||||
kerning_pair *ker = &(font->kernings[i]);
|
||||
|
||||
if (ker->first != font->last_codepoint)
|
||||
continue;
|
||||
|
||||
if (ker->second != codepoint)
|
||||
continue;
|
||||
|
||||
x1 += ker->amount;
|
||||
x2 += ker->amount;
|
||||
font->pointer_x += ker->amount;
|
||||
}
|
||||
|
||||
font->last_codepoint = codepoint;
|
||||
|
||||
GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx1), inttot16(ty1));
|
||||
GFX_VERTEX16 = (y1 << 16) | (x1 & 0xFFFF); // Up-left
|
||||
GFX_VERTEX16 = z;
|
||||
|
||||
GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx1), inttot16(ty2));
|
||||
GFX_VERTEX_XY = (y2 << 16) | (x1 & 0xFFFF); // Down-left
|
||||
|
||||
GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx2), inttot16(ty2));
|
||||
GFX_VERTEX_XY = (y2 << 16) | (x2 & 0xFFFF); // Down-right
|
||||
|
||||
GFX_TEX_COORD = TEXTURE_PACK(inttot16(tx2), inttot16(ty1));
|
||||
GFX_VERTEX_XY = (y1 << 16) | (x2 & 0xFFFF); // Up-right
|
||||
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
dsf_error DSF_StringRenderDryRun(dsf_handle handle, const char *str,
|
||||
size_t *size_x, size_t *size_y)
|
||||
{
|
||||
if ((handle == 0) || (str == NULL) || (size_x == NULL) || (size_y == NULL))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
if (strlen(str) == 0)
|
||||
{
|
||||
*size_x = 0;
|
||||
*size_y = 0;
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
dsf_error ret = DSF_NO_ERROR;
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
font->pointer_x = 0;
|
||||
font->pointer_y = 0;
|
||||
font->box_left = 0;
|
||||
font->box_top = 0;
|
||||
font->last_codepoint = 0;
|
||||
|
||||
const char *readptr = str;
|
||||
|
||||
size_t max_x = 0;
|
||||
size_t max_y = 0;
|
||||
|
||||
while (*readptr != '\0')
|
||||
{
|
||||
uint32_t codepoint;
|
||||
size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
|
||||
readptr += size;
|
||||
|
||||
ret = DSF_CodepointRenderDryRun(handle, codepoint);
|
||||
if (ret != DSF_NO_ERROR)
|
||||
break;
|
||||
|
||||
if (font->pointer_x > max_x)
|
||||
max_x = font->pointer_x;
|
||||
if (font->pointer_y > max_y)
|
||||
max_y = font->pointer_y;
|
||||
}
|
||||
|
||||
*size_x = max_x;
|
||||
*size_y = max_y + font->line_height;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsf_error DSF_StringRender3D(dsf_handle handle, const char *str,
|
||||
int32_t x, int32_t y, int32_t z)
|
||||
{
|
||||
if ((handle == 0) || (str == NULL))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
dsf_error ret = DSF_NO_ERROR;
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
font->pointer_x = x;
|
||||
font->pointer_y = y;
|
||||
font->box_left = x;
|
||||
font->box_top = y;
|
||||
font->last_codepoint = 0;
|
||||
|
||||
const char *readptr = str;
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
|
||||
while (*readptr != '\0')
|
||||
{
|
||||
uint32_t codepoint;
|
||||
size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
|
||||
readptr += size;
|
||||
|
||||
ret = DSF_CodepointRender3D(handle, codepoint, z);
|
||||
if (ret != DSF_NO_ERROR)
|
||||
break;
|
||||
}
|
||||
|
||||
glEnd();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
dsf_error DSF_StringRender3DAlpha(dsf_handle handle, const char *str,
|
||||
int32_t x, int32_t y, int32_t z,
|
||||
uint32_t poly_fmt, int poly_id_base)
|
||||
{
|
||||
if ((handle == 0) || (str == NULL))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
dsf_error ret = DSF_NO_ERROR;
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
font->pointer_x = x;
|
||||
font->pointer_y = y;
|
||||
font->box_left = x;
|
||||
font->box_top = y;
|
||||
font->last_codepoint = 0;
|
||||
|
||||
const char *readptr = str;
|
||||
|
||||
int id_index = 0;
|
||||
|
||||
while (*readptr != '\0')
|
||||
{
|
||||
uint32_t codepoint;
|
||||
size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
|
||||
readptr += size;
|
||||
|
||||
glPolyFmt(poly_fmt | POLY_ID(poly_id_base + id_index));
|
||||
id_index ^= 1;
|
||||
|
||||
glBegin(GL_QUADS);
|
||||
ret = DSF_CodepointRender3D(handle, codepoint, z);
|
||||
glEnd();
|
||||
if (ret != DSF_NO_ERROR)
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static dsf_error DSF_CodepointRenderBuffer(dsf_handle handle,
|
||||
uint32_t codepoint, unsigned int texture_fmt,
|
||||
const void *font_texture, size_t font_width, size_t font_height,
|
||||
void *out_texture, size_t out_width, size_t out_height)
|
||||
{
|
||||
// Don't check if the arguments are valid, this is an internal function
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
font->pointer_x = font->box_left;
|
||||
font->pointer_y += font->line_height;
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
int index = DSF_CodepointFindGlyphIndex(handle, codepoint);
|
||||
if (index < 0)
|
||||
return DSF_CODEPOINT_NOT_FOUND;
|
||||
|
||||
block_char *ch = &(font->chars[index]);
|
||||
|
||||
int tx1 = ch->x;
|
||||
int ty1 = ch->y;
|
||||
|
||||
int x1 = font->pointer_x;
|
||||
int x2 = x1 + ch->width;
|
||||
int y1 = font->pointer_y;
|
||||
int y2 = y1 + ch->height;
|
||||
|
||||
x1 += ch->xoffset;
|
||||
x2 += ch->xoffset;
|
||||
y1 += ch->yoffset;
|
||||
y2 += ch->yoffset;
|
||||
|
||||
font->pointer_x += ch->xadvance;
|
||||
|
||||
for (size_t i = 0; i < font->num_kernings; i++)
|
||||
{
|
||||
kerning_pair *ker = &(font->kernings[i]);
|
||||
|
||||
if (ker->first != font->last_codepoint)
|
||||
continue;
|
||||
|
||||
if (ker->second != codepoint)
|
||||
continue;
|
||||
|
||||
x1 += ker->amount;
|
||||
x2 += ker->amount;
|
||||
font->pointer_x += ker->amount;
|
||||
}
|
||||
|
||||
font->last_codepoint = codepoint;
|
||||
|
||||
if (texture_fmt == GL_RGB256)
|
||||
{
|
||||
const uint8_t *src = font_texture;
|
||||
uint8_t *dst = out_texture;
|
||||
|
||||
src += tx1 + ty1 * font_width;
|
||||
dst += x1 + y1 * out_width;
|
||||
|
||||
for (int y = 0; y <= y2 - y1; y++)
|
||||
{
|
||||
const uint8_t *src_row = src;
|
||||
uint8_t *dst_row = dst;
|
||||
|
||||
for (int x = 0; x <= x2 - x1; x++)
|
||||
{
|
||||
uint8_t color = *src_row++;
|
||||
if (color != 0)
|
||||
*dst_row = color;
|
||||
dst_row++;
|
||||
}
|
||||
|
||||
src += font_width;
|
||||
dst += out_width;
|
||||
}
|
||||
}
|
||||
else if (texture_fmt == GL_RGBA)
|
||||
{
|
||||
const uint16_t *src = font_texture;
|
||||
uint16_t *dst = out_texture;
|
||||
|
||||
src += tx1 + ty1 * font_width;
|
||||
dst += x1 + y1 * out_width;
|
||||
|
||||
for (int y = 0; y <= y2 - y1; y++)
|
||||
{
|
||||
const uint16_t *src_row = src;
|
||||
uint16_t *dst_row = dst;
|
||||
|
||||
for (int x = 0; x <= x2 - x1; x++)
|
||||
{
|
||||
uint16_t color = *src_row++;
|
||||
if (color & BIT(15))
|
||||
*dst_row = color;
|
||||
dst_row++;
|
||||
}
|
||||
|
||||
src += font_width;
|
||||
dst += out_width;
|
||||
}
|
||||
}
|
||||
else if ((texture_fmt == GL_RGB32_A3) || (texture_fmt == GL_RGB8_A5))
|
||||
{
|
||||
const uint8_t *src = font_texture;
|
||||
uint8_t *dst = out_texture;
|
||||
|
||||
src += tx1 + ty1 * font_width;
|
||||
dst += x1 + y1 * out_width;
|
||||
|
||||
int alpha_shift;
|
||||
|
||||
if (texture_fmt == GL_RGB32_A3)
|
||||
alpha_shift = 5;
|
||||
else // if (texture_fmt == GL_RGB8_A5)
|
||||
alpha_shift = 3;
|
||||
|
||||
for (int y = 0; y <= y2 - y1; y++)
|
||||
{
|
||||
const uint8_t *src_row = src;
|
||||
uint8_t *dst_row = dst;
|
||||
|
||||
for (int x = 0; x <= x2 - x1; x++)
|
||||
{
|
||||
uint8_t color = *src_row++;
|
||||
// We can't really blend two different colors because we're
|
||||
// limited by the palette. For that reason, if the new alpha is
|
||||
// 0 we leave the old entry. If the new alpha is anything other
|
||||
// than 0, we replace the old entry by the new entry, even if
|
||||
// the result is incorrect.
|
||||
if ((color >> alpha_shift) != 0)
|
||||
*dst_row = color;
|
||||
dst_row++;
|
||||
}
|
||||
|
||||
src += font_width;
|
||||
dst += out_width;
|
||||
}
|
||||
}
|
||||
else if (texture_fmt == GL_RGB16)
|
||||
{
|
||||
const uint8_t *src = font_texture;
|
||||
uint8_t *dst = out_texture;
|
||||
|
||||
for (int y = 0; y <= y2 - y1; y++)
|
||||
{
|
||||
const uint8_t *src_row = src + (((ty1 + y) * font_width) >> 1);
|
||||
uint8_t *dst_row = dst + (((y1 + y) * out_width) >> 1);
|
||||
|
||||
for (int x = 0; x <= x2 - x1; x++)
|
||||
{
|
||||
const uint8_t *src_px = src_row + ((tx1 + x) >> 1);
|
||||
uint8_t *dst_px = dst_row + ((x1 + x) >> 1);
|
||||
|
||||
int shift = ((tx1 + x) & 1) * 4;
|
||||
uint8_t src_color = (*src_px >> shift) & 0xF;
|
||||
|
||||
if (src_color == 0)
|
||||
continue;
|
||||
|
||||
shift = ((x1 + x) & 1) * 4;
|
||||
uint8_t mask = ~(0xF << shift);
|
||||
*dst_px = (*dst_px & mask) | (src_color << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (texture_fmt == GL_RGB4)
|
||||
{
|
||||
const uint8_t *src = font_texture;
|
||||
uint8_t *dst = out_texture;
|
||||
|
||||
for (int y = 0; y <= y2 - y1; y++)
|
||||
{
|
||||
const uint8_t *src_row = src + (((ty1 + y) * font_width) >> 2);
|
||||
uint8_t *dst_row = dst + (((y1 + y) * out_width) >> 2);
|
||||
|
||||
for (int x = 0; x <= x2 - x1; x++)
|
||||
{
|
||||
const uint8_t *src_px = src_row + ((tx1 + x) >> 2);
|
||||
uint8_t *dst_px = dst_row + ((x1 + x) >> 2);
|
||||
|
||||
int shift = ((tx1 + x) & 3) * 2;
|
||||
uint8_t src_color = (*src_px >> shift) & 0x3;
|
||||
|
||||
if (src_color == 0)
|
||||
continue;
|
||||
|
||||
shift = ((x1 + x) & 3) * 2;
|
||||
uint8_t mask = ~(0x3 << shift);
|
||||
*dst_px = (*dst_px & mask) | (src_color << shift);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DSF_NO_ERROR;
|
||||
}
|
||||
|
||||
dsf_error DSF_StringRenderToTexture(dsf_handle handle,
|
||||
const char *str, unsigned int texture_fmt,
|
||||
const void *font_texture, size_t font_width, size_t font_height,
|
||||
void **out_texture, size_t *out_width, size_t *out_height)
|
||||
{
|
||||
if ((handle == 0) || (str == NULL) || (font_texture == NULL) ||
|
||||
(out_texture == NULL) || (out_width == NULL) || (out_height == NULL))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
if ((texture_fmt < 1) || (texture_fmt > 7) || (texture_fmt == GL_COMPRESSED))
|
||||
return DSF_TEXTURE_BAD_FORMAT;
|
||||
|
||||
if ((font_width == 0) || (font_height == 0))
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
if (strlen(str) == 0)
|
||||
return DSF_INVALID_ARGUMENT;
|
||||
|
||||
// Start process
|
||||
|
||||
dsf_error ret = DSF_NO_ERROR;
|
||||
|
||||
dsf_font_internal_state *font = (dsf_font_internal_state *)handle;
|
||||
|
||||
// Get size
|
||||
|
||||
size_t tex_width, tex_height;
|
||||
|
||||
ret = DSF_StringRenderDryRun(handle, str, &tex_width, &tex_height);
|
||||
if (ret != DSF_NO_ERROR)
|
||||
return ret;
|
||||
|
||||
if ((tex_width > 1024) || (tex_height > 1024))
|
||||
return DSF_TEXTURE_TOO_BIG;
|
||||
|
||||
// Expand to a valid texture size
|
||||
|
||||
for (size_t i = 8; i <= 1024; i <<= 1)
|
||||
{
|
||||
if (tex_width <= i)
|
||||
{
|
||||
tex_width = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 8; i <= 1024; i <<= 1)
|
||||
{
|
||||
if (tex_height <= i)
|
||||
{
|
||||
tex_height = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const int size_shift[] = {
|
||||
0, // Nothing
|
||||
1, // A3PAL32
|
||||
3, // PAL4
|
||||
2, // PAL16
|
||||
1, // PAL256
|
||||
0, // TEX4X4 (This value isn't used)
|
||||
1, // A5PAL8
|
||||
0, // A1RGB5
|
||||
0, // RGB5
|
||||
};
|
||||
|
||||
size_t tex_size = (2 * tex_width * tex_height) >> size_shift[texture_fmt];
|
||||
|
||||
// Allocate buffer
|
||||
|
||||
void *tex_buffer = calloc(1, tex_size);
|
||||
memset(tex_buffer, 0, tex_size);
|
||||
if (tex_buffer == NULL)
|
||||
return DSF_NO_MEMORY;
|
||||
|
||||
// Render string
|
||||
|
||||
font->pointer_x = 0;
|
||||
font->pointer_y = 0;
|
||||
font->box_left = 0;
|
||||
font->box_top = 0;
|
||||
font->last_codepoint = 0;
|
||||
|
||||
const char *readptr = str;
|
||||
|
||||
while (*readptr != '\0')
|
||||
{
|
||||
uint32_t codepoint;
|
||||
size_t size = DSF_UTF8_CodepointRead(readptr, &codepoint);
|
||||
readptr += size;
|
||||
|
||||
ret = DSF_CodepointRenderBuffer(handle, codepoint, texture_fmt,
|
||||
font_texture, font_width, font_height,
|
||||
tex_buffer, tex_width, tex_height);
|
||||
if (ret != DSF_NO_ERROR)
|
||||
break;
|
||||
}
|
||||
|
||||
// Return texture information
|
||||
|
||||
*out_texture = tex_buffer;
|
||||
*out_width = tex_width;
|
||||
*out_height = tex_height;
|
||||
|
||||
return ret;
|
||||
}
|
173
source/libdsf/dsf.h
Normal file
173
source/libdsf/dsf.h
Normal file
@ -0,0 +1,173 @@
|
||||
// SPDX-License-Identifier: Zlib
|
||||
//
|
||||
// Copyright (c) 2024 Antonio Niño Díaz
|
||||
|
||||
#ifndef DSF_H__
|
||||
#define DSF_H__
|
||||
|
||||
/// @file dsf.h
|
||||
///
|
||||
/// @brief Global include of LibDSF.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/// @defgroup libdsf_types LibDSF definitions and types.
|
||||
/// @{
|
||||
|
||||
/// Major version of LibDSF (semver)
|
||||
#define LIBDSF_VERSION_MAJOR 0
|
||||
/// Minor version of LibDSF (semver)
|
||||
#define LIBDSF_VERSION_MINOR 1
|
||||
/// Patch version of LibDSF (semver)
|
||||
#define LIBDSF_VERSION_PATCH 0
|
||||
|
||||
/// Value that combines all version numbers, useful for version checks.
|
||||
#define LIBDSF_VERSION \
|
||||
((LIBDSF_VERSION_MAJOR << 16) | \
|
||||
(LIBDSF_VERSION_MINOR << 8) | \
|
||||
(LIBDSF_VERSION_PATCH << 0))
|
||||
|
||||
/// Version string
|
||||
#define LIBDSF_VERSION_STRING "0.1.0"
|
||||
|
||||
/// Character that is used when there are UTF-8 decoding errors.
|
||||
///
|
||||
/// This is returned when a character with an invalid encoding is found.
|
||||
#define REPLACEMENT_CHARACTER 0xFFFD
|
||||
|
||||
/// List of possible errors of the library.
|
||||
typedef enum {
|
||||
DSF_NO_ERROR = 0, ///< No error
|
||||
DSF_INVALID_ARGUMENT = -1, ///< Invalid argument (e.g. NULL pointer)
|
||||
DSF_BAD_MAGIC = -2, ///< Bad BMFont magic number
|
||||
DSF_BAD_VERSION = -3, ///< Bad BMFont version
|
||||
DSF_BAD_CHUNK_SIZE = -4, ///< The size of a BMFont chunk is wrong
|
||||
DSF_NO_MEMORY = -5, ///< Not enough RAM to allocate font data
|
||||
DSF_UNEXPECTED_END = -6, ///< File ends unexpectedly
|
||||
DSF_NO_CHARACTERS = -7, ///< No characters present in font
|
||||
DSF_FILE_OPEN_ERROR = -8, ///< Can't open the provided file
|
||||
DSF_FILE_EMPTY = -9, ///< The provided file is empty
|
||||
DSF_FILE_SEEK_ERROR = -10, ///< Can't fseek() file
|
||||
DSF_FILE_READ_ERROR = -11, ///< Can't read file
|
||||
DSF_CODEPOINT_NOT_FOUND = -12, ///< Codepoint not present in font
|
||||
DSF_TEXTURE_TOO_BIG = -12, ///< The texture size required is too big for the NDS
|
||||
DSF_TEXTURE_BAD_FORMAT = -13, ///< Unsupported NDS texture format
|
||||
} dsf_error;
|
||||
|
||||
/// Type that represents a DSF font internal state.
|
||||
typedef uintptr_t dsf_handle;
|
||||
|
||||
/// @}
|
||||
/// @defgroup libdsf_load_unload Font loading and unloading functions.
|
||||
/// @{
|
||||
|
||||
/// This function loads a BMFont in binary format from RAM.
|
||||
///
|
||||
/// @param handle Pointer to a handle that will be configured for this font.
|
||||
/// @param data Pointer to the font data (".fnt" file).
|
||||
/// @param data_size Size of the font data.
|
||||
///
|
||||
/// @return An error code or DSF_NO_ERROR on success.
|
||||
dsf_error DSF_LoadFontMemory(dsf_handle *handle,
|
||||
const void *data, int32_t data_size);
|
||||
|
||||
/// This function loads a BMFont in binary format from the filesystem.
|
||||
///
|
||||
/// @param handle Pointer to a handle that will be configured for this font.
|
||||
/// @param path Path to the ".fnt" file in the filesystem.
|
||||
///
|
||||
/// @return An error code or DSF_NO_ERROR on success.
|
||||
dsf_error DSF_LoadFontFilesystem(dsf_handle *handle, const char *path);
|
||||
|
||||
/// Free all memory used by this font.
|
||||
///
|
||||
/// It also invalidates the provided handler.
|
||||
///
|
||||
/// @param handle Pointer to the font handler.
|
||||
///
|
||||
/// @return An error code or DSF_NO_ERROR on success.
|
||||
dsf_error DSF_FreeFont(dsf_handle *handle);
|
||||
|
||||
/// @}
|
||||
/// @defgroup libdsf_render Functions to draw text strings.
|
||||
/// @{
|
||||
|
||||
/// Pretend to render a string to calculate its final size once rendered.
|
||||
///
|
||||
/// @param handle Handler of the font to use.
|
||||
/// @param str String to print.
|
||||
/// @param size_x Pointer to a variable to store the size.
|
||||
/// @param size_y Pointer to a variable to store the size.
|
||||
///
|
||||
/// @return An error code or DSF_NO_ERROR on success.
|
||||
dsf_error DSF_StringRenderDryRun(dsf_handle handle, const char *str,
|
||||
size_t *size_x, size_t *size_y);
|
||||
|
||||
/// Render a string by rendering one 3D quad per codepoint.
|
||||
///
|
||||
/// @param handle Handler of the font to use.
|
||||
/// @param str String to print.
|
||||
/// @param x Top x coordinate (0 to 255, but you can go outside of that).
|
||||
/// @param y Left y coordinate (0 to 191, but you can go outside of that).
|
||||
/// @param z Z coordinate (depth).
|
||||
///
|
||||
/// @return An error code or DSF_NO_ERROR on success.
|
||||
dsf_error DSF_StringRender3D(dsf_handle handle, const char *str,
|
||||
int32_t x, int32_t y, int32_t z);
|
||||
|
||||
/// Render a string by rendering one 3D quad per codepoint with alternating
|
||||
/// polygon IDs.
|
||||
///
|
||||
/// This function will alternate between polygon IDs so that alpha blending
|
||||
/// works between multiple polygons when they overlap. This is a requirement of
|
||||
/// the NDS 3D hardware.
|
||||
///
|
||||
/// It is required to pass the base polygon format as a parameter because the
|
||||
/// polygon format data is write-only. Whenever the polygon ID needs to be
|
||||
/// changed, the rest of the polygon format flags need to be set as well.
|
||||
///
|
||||
/// @param handle Handler of the font to use.
|
||||
/// @param str String to print.
|
||||
/// @param x Top x coordinate (0 to 255, but you can go outside of that).
|
||||
/// @param y Left y coordinate (0 to 191, but you can go outside of that).
|
||||
/// @param z Z coordinate (depth).
|
||||
/// @param poly_fmt Polygon formats to apply to the characters.
|
||||
/// @param poly_id_base poly_id_base and poly_id_base + 1 will be used.
|
||||
///
|
||||
/// @return An error code or DSF_NO_ERROR on success.
|
||||
dsf_error DSF_StringRender3DAlpha(dsf_handle handle, const char *str,
|
||||
int32_t x, int32_t y, int32_t z,
|
||||
uint32_t poly_fmt, int poly_id_base);
|
||||
|
||||
/// Allocates a buffer and renders the provided string to that buffer.
|
||||
///
|
||||
/// This function takes a texture stored in RAM with the desired font. It will
|
||||
/// calculate the final size of the text it has to print, and it will allocate
|
||||
/// enough space for it. Note that NDS texture sizes must be powers of two, so
|
||||
/// the end result may be bigger than the actual text. After allocating the
|
||||
/// buffer, it will render the string to that buffer.
|
||||
///
|
||||
/// This function won't load the texture to VRAM. The returned buffer needs to
|
||||
/// be loaded to texture VRAM to be used (with functions like glTexImage2D() or
|
||||
/// NE_MaterialTexLoad()).
|
||||
///
|
||||
/// @param handle Handler of the font to use.
|
||||
/// @param str String to print.
|
||||
/// @param texture_fmt Texture format (GL_TEXTURE_TYPE_ENUM, NE_TextureFormat).
|
||||
/// @param font_texture Pointer to the font texture data that contains the.
|
||||
/// @param font_width Width of the font texture.
|
||||
/// @param font_height Height of the font texture.
|
||||
/// @param out_texture The pointer to the new buffer is returned here.
|
||||
/// @param out_width The width to the new buffer is returned here.
|
||||
/// @param out_height The height to the new buffer is returned here.
|
||||
///
|
||||
/// @return An error code or DSF_NO_ERROR on success.
|
||||
dsf_error DSF_StringRenderToTexture(dsf_handle handle,
|
||||
const char *str, unsigned int texture_fmt,
|
||||
const void *font_texture, size_t font_width, size_t font_height,
|
||||
void **out_texture, size_t *out_width, size_t *out_height);
|
||||
|
||||
/// @}
|
||||
|
||||
#endif // DSF_H__
|
Loading…
Reference in New Issue
Block a user