mirror of
https://github.com/AntonioND/nitro-engine.git
synced 2025-06-18 08:35:44 -04:00

The goal of the changes made in this PR is to create something with this effect ("Jonko" is in in green, the rest is in white): "Dear god." Jonko quickly replied, crestfallen. In order to do this, I need to call `NE_RichTextRender3D` or `NE_RichTextRender3DAlpha` three times: once to draw the first part of the sentence with the white font, once to draw "Jonko" in turquoise, and once to draw the remainder of the sentence in white again. In order to ensure that text is being drawn in the correct location, I need the position where the previous call stopped drawing, which leads to the first change this PR makes: I have added a version of `DSF_StringRenderDryRun` which accepts pointers to `final_x` and `final_y` and stores the last position of `font->pointer_x` and `font->pointer_y` in them and I have added a new variant of `NE_RichTextRenderDryRun` called `NE_RichTextRenderDryRunWithPos` which uses that DSF function and has analogous parameters. These functions get us the position where we need to place the next cursor, but the next issue is that currently NitroEngine and libDSF only allow for specifying the position of the overall box to draw text in, but not where to offset the cursor in that box. This leads to the following problem ("Jonko" is in in green, the rest is in white): "Dear god." Jonko quickly replied, crestfallen. Thus, the second change: I have introduced new versions of `DSF_StringRender3D` and `DSF_StringRender3DAlpha` which accept an `xStart` position that is added to the `pointerX`. Correspondingly, I have added versions of `NE_RichTextRender3D` and `NE_RichTextRender3DAlpha` that use these new functions as well. Let me know if there are any issues with how I've written this (C is not my forte, after all), but the first screenshot is this being tested in production code.
513 lines
13 KiB
C
513 lines
13 KiB
C
// 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
|
|
bool has_to_free_buffers;
|
|
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 u32 NE_NumRichTextSlots = 0;
|
|
|
|
static ne_rich_textinfo_t *NE_RichTextInfo;
|
|
|
|
static int NE_RICH_TEXT_PRIORITY = 0;
|
|
|
|
void NE_RichTextPrioritySet(int priority)
|
|
{
|
|
NE_RICH_TEXT_PRIORITY = priority;
|
|
}
|
|
|
|
void NE_RichTextPriorityReset(void)
|
|
{
|
|
NE_RICH_TEXT_PRIORITY = 0;
|
|
}
|
|
|
|
void NE_RichTextInit(u32 slot)
|
|
{
|
|
// Compatibility mode -- if someone tries to allocate a slot
|
|
// without having called start system, we allocate the old maximum
|
|
// number of slots for safety
|
|
if (NE_NumRichTextSlots == 0)
|
|
NE_RichTextStartSystem(NE_DEFAULT_RICH_TEXT_FONTS);
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
{
|
|
NE_DebugPrint("Attempted to initialize a slot greater than the number of slots allocated; skipping");
|
|
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;
|
|
|
|
NE_RichTextPriorityReset();
|
|
}
|
|
|
|
int NE_RichTextEnd(u32 slot)
|
|
{
|
|
if (slot >= NE_NumRichTextSlots)
|
|
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->has_to_free_buffers)
|
|
{
|
|
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_RichTextStartSystem(u32 numSlots)
|
|
{
|
|
NE_NumRichTextSlots = numSlots;
|
|
NE_RichTextInfo = calloc(sizeof(ne_rich_textinfo_t), NE_NumRichTextSlots);
|
|
if (NE_RichTextInfo == NULL)
|
|
{
|
|
NE_DebugPrint("Failed to allocate array for NE_RichTextInfo");
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void NE_RichTextResetSystem(void)
|
|
{
|
|
for (int i = 0; i < NE_NumRichTextSlots; i++)
|
|
NE_RichTextEnd(i);
|
|
free(NE_RichTextInfo);
|
|
NE_NumRichTextSlots = 0;
|
|
}
|
|
|
|
int NE_RichTextMetadataLoadFAT(u32 slot, const char *path)
|
|
{
|
|
NE_AssertPointer(path, "NULL path pointer");
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
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 1;
|
|
}
|
|
|
|
int NE_RichTextMetadataLoadMemory(u32 slot, const void *data, size_t data_size)
|
|
{
|
|
NE_AssertPointer(data, "NULL data pointer");
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
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_NumRichTextSlots)
|
|
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_NumRichTextSlots)
|
|
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)slot;
|
|
(void)path;
|
|
NE_DebugPrint("%s only supported in BlocksDS", __func__);
|
|
return 0;
|
|
#else // NE_BLOCKSDS
|
|
NE_AssertPointer(path, "NULL path pointer");
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
return 0;
|
|
|
|
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
|
if (!info->active)
|
|
return 0;
|
|
|
|
if (info->has_to_free_buffers)
|
|
{
|
|
if (info->texture_buffer != NULL)
|
|
free(info->texture_buffer);
|
|
if (info->palette_buffer != NULL)
|
|
free(info->palette_buffer);
|
|
}
|
|
|
|
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 error;
|
|
}
|
|
|
|
if (gfxDst == NULL)
|
|
{
|
|
NE_DebugPrint("No graphics found in GRF file");
|
|
goto error;
|
|
}
|
|
|
|
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 error;
|
|
}
|
|
|
|
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 error;
|
|
}
|
|
|
|
info->palette_buffer = NULL;
|
|
info->palette_size = 0;
|
|
}
|
|
|
|
info->has_to_free_buffers = true;
|
|
|
|
return 1; // Success
|
|
|
|
error:
|
|
free(gfxDst);
|
|
free(palDst);
|
|
return 0;
|
|
#endif // NE_BLOCKSDS
|
|
}
|
|
|
|
int NE_RichTextBitmapSet(u32 slot, const void *texture_buffer,
|
|
size_t texture_width, size_t texture_height,
|
|
NE_TextureFormat texture_fmt,
|
|
const void *palette_buffer, size_t palette_size)
|
|
{
|
|
NE_AssertPointer(texture_buffer, "NULL texture pointer");
|
|
NE_AssertPointer(palette_buffer, "NULL palette pointer");
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
return 0;
|
|
|
|
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
|
if (!info->active)
|
|
return 0;
|
|
|
|
if (info->has_to_free_buffers)
|
|
{
|
|
if (info->texture_buffer != NULL)
|
|
free(info->texture_buffer);
|
|
if (info->palette_buffer != NULL)
|
|
free(info->palette_buffer);
|
|
}
|
|
|
|
info->has_to_free_buffers = false;
|
|
|
|
info->texture_buffer = (void *)texture_buffer;
|
|
info->texture_width = texture_width;
|
|
info->texture_height = texture_height;
|
|
info->fmt = texture_fmt;
|
|
info->palette_buffer = (void *)palette_buffer;
|
|
info->palette_size = palette_size;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int NE_RichTextRenderDryRunWithPos(u32 slot, const char *str,
|
|
size_t *size_x, size_t *size_y,
|
|
size_t *final_x, size_t *final_y)
|
|
{
|
|
NE_AssertPointer(str, "NULL str pointer");
|
|
NE_AssertPointer(size_x, "NULL size X pointer");
|
|
NE_AssertPointer(size_y, "NULL size Y pointer");
|
|
NE_AssertPointer(final_x, "NULL final X pointer");
|
|
NE_AssertPointer(final_y, "NULL final Y pointer");
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
return 0;
|
|
|
|
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
|
if (!info->active)
|
|
return 0;
|
|
|
|
dsf_error err = DSF_StringRenderDryRunWithCursor(info->handle, str,
|
|
size_x, size_y,
|
|
final_x, final_y);
|
|
if (err != DSF_NO_ERROR)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int NE_RichTextRenderDryRun(u32 slot, const char *str,
|
|
size_t *size_x, size_t *size_y)
|
|
{
|
|
size_t final_x, final_y;
|
|
return NE_RichTextRenderDryRunWithPos(slot, str, size_x, size_y, &final_x, &final_y);
|
|
}
|
|
|
|
int NE_RichTextRender3DWithIndent(u32 slot, const char *str, s32 x, s32 y,
|
|
s32 xIndent)
|
|
{
|
|
NE_AssertPointer(str, "NULL str pointer");
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
return 0;
|
|
|
|
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
|
if (!info->active)
|
|
return 0;
|
|
|
|
NE_MaterialUse(info->material);
|
|
|
|
dsf_error err = DSF_StringRender3DWithIndent(info->handle, str, x, y,
|
|
NE_RICH_TEXT_PRIORITY, xIndent);
|
|
if (err != DSF_NO_ERROR)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int NE_RichTextRender3D(u32 slot, const char *str, s32 x, s32 y)
|
|
{
|
|
return NE_RichTextRender3DWithIndent(slot, str, x, y, 0);
|
|
}
|
|
|
|
int NE_RichTextRender3DAlphaWithIndent(u32 slot, const char *str, s32 x, s32 y,
|
|
uint32_t poly_fmt, int poly_id_base,
|
|
s32 xIndent)
|
|
{
|
|
NE_AssertPointer(str, "NULL str pointer");
|
|
|
|
if (slot >= NE_NumRichTextSlots)
|
|
return 0;
|
|
|
|
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
|
if (!info->active)
|
|
return 0;
|
|
|
|
NE_MaterialUse(info->material);
|
|
|
|
dsf_error err = DSF_StringRender3DAlphaWithIndent(info->handle, str, x, y,
|
|
NE_RICH_TEXT_PRIORITY,
|
|
poly_fmt, poly_id_base, xIndent);
|
|
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)
|
|
{
|
|
return NE_RichTextRender3DAlphaWithIndent(slot, str, x, y, poly_fmt, poly_id_base, 0);
|
|
}
|
|
|
|
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_NumRichTextSlots)
|
|
return 0;
|
|
|
|
ne_rich_textinfo_t *info = &NE_RichTextInfo[slot];
|
|
if (!info->active)
|
|
return 0;
|
|
|
|
void *out_texture = NULL;
|
|
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;
|
|
}
|