nds_nflib/source/nf_text.c
Antonio Niño Díaz d892d6cc8a library: Fix '\n' in NF_WriteText16()
Also, slightly rework the code of NF_WriteText() to match the code sytle
of the rest of the library.
2024-07-06 12:44:24 +01:00

439 lines
13 KiB
C

// SPDX-License-Identifier: MIT
//
// Copyright (c) 2009-2014 Cesar Rincon "NightFox"
// Copyright (c) 2023 Antonio Niño Díaz "AntonioND"
//
// NightFox LIB - 8x8 font text support functions
// http://www.nightfoxandco.com/
#include <stdio.h>
#include <string.h>
#include <nds.h>
#include "nf_2d.h"
#include "nf_basic.h"
#include "nf_text.h"
#include "nf_tiledbg.h"
// Structs that hold information about all text layers
NF_TYPE_TEXT_INFO NF_TEXT[2][4];
void NF_InitTextSys(int screen)
{
for (int n = 0; n < 4; n++)
{
NF_TEXT[screen][n].width = 0;
NF_TEXT[screen][n].height = 0;
NF_TEXT[screen][n].rotation = 0;
NF_TEXT[screen][n].slot = 255;
NF_TEXT[screen][n].pal = 0;
NF_TEXT[screen][n].exist = false;
NF_TEXT[screen][n].update = false;
}
}
void NF_LoadTextFont(const char *file, const char *name, u32 width, u32 height,
u32 rotation)
{
// Look for a free slot
u32 slot = 255;
for (int n = 0; n < NF_SLOTS_TBG; n ++)
{
// If a slot is free, mark it as in use and stop the search
if (NF_TILEDBG[n].available)
{
NF_TILEDBG[n].available = false;
slot = n;
break;
}
}
// If there are no free slots, fail
if (slot == 255)
NF_Error(103, "Tiled Bg", NF_SLOTS_TBG);
// Verify that the background is a multiple of 256 pixels (32 tiles)
if (((width % 256) != 0) || ((height % 256) != 0))
NF_Error(115, file, 0);
// Free buffers if they are in use
free(NF_BUFFER_BGMAP[slot]);
NF_BUFFER_BGMAP[slot] = NULL;
free(NF_BUFFER_BGTILES[slot]);
NF_BUFFER_BGTILES[slot] = NULL;
free(NF_BUFFER_BGPAL[slot]);
NF_BUFFER_BGPAL[slot] = NULL;
// File path
char filename[256];
// Load .FNT/.IMG file
snprintf(filename, sizeof(filename), "%s/%s.fnt", NF_ROOTFOLDER, file);
FILE *file_id = fopen(filename, "rb");
if (file_id == NULL) // If the file can't be opened
{
snprintf(filename, sizeof(filename), "%s/%s.img", NF_ROOTFOLDER, file);
file_id = fopen(filename, "rb");
if (file_id == NULL) // If the file can't be opened
NF_Error(101, filename, 0);
}
// Get file size
NF_TILEDBG[slot].tilesize = NF_TEXT_FONT_CHARS * 64;
// Allocate space in RAM
NF_BUFFER_BGTILES[slot] = malloc(NF_TILEDBG[slot].tilesize);
if (NF_BUFFER_BGTILES[slot] == NULL) // If there is not enough RAM
NF_Error(102, NULL, NF_TILEDBG[slot].tilesize);
// Read file to RAM
fread(NF_BUFFER_BGTILES[slot], 1, NF_TILEDBG[slot].tilesize, file_id);
fclose(file_id);
// Rotate graphics if requested
if (rotation > 0)
{
for (int n = 0; n < NF_TEXT_FONT_CHARS; n++)
NF_RotateTileGfx(slot, n, rotation);
}
// Create an empty map in RAM. Each map entry is 2 bytes in size
NF_TILEDBG[slot].mapsize = ((width / 8) * (height / 8)) * 2;
// Allocate space in RAM and zero it (calloc returns zeroed memory)
NF_BUFFER_BGMAP[slot] = calloc(NF_TILEDBG[slot].mapsize, sizeof(char));
if (NF_BUFFER_BGMAP[slot] == NULL) // If there is not enough RAM
NF_Error(102, NULL, NF_TILEDBG[slot].mapsize);
// Load .PAL file
snprintf(filename, sizeof(filename), "%s/%s.pal", NF_ROOTFOLDER, file);
NF_FileLoad(filename, &NF_BUFFER_BGPAL[slot], &NF_TILEDBG[slot].palsize, 0);
// Save background name
snprintf(NF_TILEDBG[slot].name, sizeof(NF_TILEDBG[slot].name), "%s", name);
// Save the size
NF_TILEDBG[slot].width = width;
NF_TILEDBG[slot].height = height;
}
void NF_UnloadTextFont(const char *name)
{
NF_UnloadTiledBg(name);
}
void NF_CreateTextLayer(int screen, u32 layer, u32 rotation, const char *name)
{
// Create a background to use it as a text layer
NF_CreateTiledBg(screen, layer, name);
u32 slot = 255;
// Look for the tiled background with the specified name
for (int n = 0; n < NF_SLOTS_TBG; n ++)
{
if (strcmp(name, NF_TILEDBG[n].name) == 0)
{
slot = n;
break;
}
}
// If it hasn't been found, fail
if (slot == 255)
NF_Error(103, "Tiled BG", NF_SLOTS_TBG);
NF_TEXT[screen][layer].rotation = rotation;
// Save the background size in tiles (save the index of the last row/column)
NF_TEXT[screen][layer].width = (NF_TILEDBG[slot].width / 8) - 1;
NF_TEXT[screen][layer].height = (NF_TILEDBG[slot].height / 8) - 1;
// Save slot where the font is stored
NF_TEXT[screen][layer].slot = slot;
// Mark layer as used
NF_TEXT[screen][layer].exist = true;
}
void NF_DeleteTextLayer(int screen, u32 layer)
{
// Verify that the selected text layer exists
if (!NF_TEXT[screen][layer].exist)
NF_Error(114, NULL, screen);
// Delete the tiled background used as text layer
NF_DeleteTiledBg(screen, layer);
NF_TEXT[screen][layer].rotation = 0;
NF_TEXT[screen][layer].width = 0;
NF_TEXT[screen][layer].height = 0;
// Mark layer as unused
NF_TEXT[screen][layer].exist = false;
}
void NF_WriteText(int screen, u32 layer, u32 x, u32 y, const char *text)
{
// Verify that the selected text layer exists
if (!NF_TEXT[screen][layer].exist)
NF_Error(114, NULL, screen);
u32 tsize = strlen(text); // Size of the temporary string buffer
u8 *string = malloc(tsize); // Temporary string buffer
if (string == NULL)
NF_Error(102, NULL, tsize);
// Store the text string in the temporary buffer
for (u32 n = 0; n < tsize; n++)
{
int value = 0;
// If newline found, put the character to the newline. If not, skip the
// first 32 characters.
if (text[n] == '\n')
value = '\n';
else
value = text[n] - 32;
if (value < 0)
value = 0;
string[n] = value;
// Handle special characters (newline characters are special too)
if ((string[n] > 95) || (string[n] == 10))
{
switch (text[n])
{
case '\n':
string[n] = 200;
break;
case 199: // Ç
string[n] = 96;
break;
case 231: // ç
string[n] = 97;
break;
case 209: // Ñ
string[n] = 98;
break;
case 241: // ñ
string[n] = 99;
break;
case 193: // Á
string[n] = 100;
break;
case 201: // É
string[n] = 101;
break;
case 205: // Í
string[n] = 102;
break;
case 211: // Ó
string[n] = 103;
break;
case 218: // Ú
string[n] = 104;
break;
case 225: // á
string[n] = 105;
break;
case 233: // é
string[n] = 106;
break;
case 237: // í
string[n] = 107;
break;
case 243: // ó
string[n] = 108;
break;
case 250: // ú
string[n] = 109;
break;
case 239: // ï
string[n] = 110;
break;
case 252: // ü
string[n] = 111;
break;
case 161: // ¡
string[n] = 112;
break;
case 191: // ¿
string[n] = 113;
break;
default:
string[n] = 0;
break;
}
}
}
// Variables to calculate the position of the text
s32 tx, ty;
// Write text in the background map, according to the specified rotation
switch (NF_TEXT[screen][layer].rotation)
{
case 0: // No rotation
tx = x;
ty = y;
for (u32 n = 0; n < tsize; n++)
{
// If it's a valid character, put character
if (string[n] <= NF_TEXT_FONT_LAST_VALID_CHAR)
{
NF_SetTileOfMap(screen,layer, tx, ty,
(NF_TEXT[screen][layer].pal << 12) | string[n]);
tx++;
}
// If the end of the line is reached or a newline character is found
if ((tx > NF_TEXT[screen][layer].width) || (string[n] == 200))
{
tx = 0;
ty++;
// If the last row is reached, return to the first one
if (ty > NF_TEXT[screen][layer].height)
ty = 0;
}
}
break;
case 1: // 90 degrees of clockwise rotation
tx = NF_TEXT[screen][layer].width - y;
ty = x;
for (u32 n = 0; n < tsize; n++)
{
// If it's a valid character, put character
if (string[n] <= NF_TEXT_FONT_LAST_VALID_CHAR)
{
NF_SetTileOfMap(screen,layer, tx, ty,
(NF_TEXT[screen][layer].pal << 12) | string[n]);
ty++;
}
// If the end of the line is reached or a newline character is found
if ((ty > NF_TEXT[screen][layer].height) || (string[n] == 200))
{
ty = 0;
tx--;
// If the last row is reached, return to the first one
if (tx < 0)
tx = NF_TEXT[screen][layer].width;
}
}
break;
case 2: // 90 degrees of counter-clockwise rotation
tx = y;
ty = NF_TEXT[screen][layer].height - x;
for (u32 n = 0; n < tsize; n ++)
{
// If it's a valid character, put character
if (string[n] <= NF_TEXT_FONT_LAST_VALID_CHAR)
{
NF_SetTileOfMap(screen,layer, tx, ty,
(NF_TEXT[screen][layer].pal << 12) | string[n]);
ty --;
}
// If the end of the line is reached or a newline character is found
if ((ty < 0) || (string[n] == 200))
{
ty = NF_TEXT[screen][layer].height;
tx++;
// If the last row is reached, return to the first one
if (tx > NF_TEXT[screen][layer].width)
tx = 0;
}
}
break;
}
// Mark this layer as needing an update
NF_TEXT[screen][layer].update = true;
// Free temporary buffer
free(string);
}
void NF_UpdateTextLayers(void)
{
for (int screen = 0; screen < 2; screen++)
{
for (int layer = 0; layer < 4; layer++)
{
// If the layer needs to be updated, update it and mark it as not
// requiring an update.
if (NF_TEXT[screen][layer].update)
{
NF_UpdateVramMap(screen, layer);
NF_TEXT[screen][layer].update = false;
}
}
}
}
void NF_ClearTextLayer(int screen, u32 layer)
{
// Verify that the selected text layer exists
if (!NF_TEXT[screen][layer].exist)
NF_Error(114, NULL, screen);
// Calculate buffer size (2 bytes per tile)
u32 size = (NF_TEXT[screen][layer].width + 1) * (NF_TEXT[screen][layer].height + 1) * 2;
// Zero the map
memset(NF_BUFFER_BGMAP[NF_TEXT[screen][layer].slot], 0, size);
// Mark this layer as needing an update
NF_TEXT[screen][layer].update = true;
}
void NF_DefineTextColor(int screen, u32 layer, u32 color, u32 r, u32 g, u32 b)
{
// Verify that the selected text layer exists
if (!NF_TEXT[screen][layer].exist)
NF_Error(114, NULL, screen);
// Pack RGB value
u32 rgb = r | (g << 5) | (b << 10);
// Modify the palette of the selected screen
if (screen == 0)
{
vramSetBankE(VRAM_E_LCD); // Enable CPU access to VRAM_E
u32 address = 0x06880000 + (layer << 13) + (color * 256 * 2); // First color
*((u16*)address) = (u16)0xFF00FF;
address = 0x06880000 + (layer << 13) + (color * 256 * 2) + 2; // Second color
*((u16*)address) = rgb;
vramSetBankE(VRAM_E_BG_EXT_PALETTE);
}
else
{
vramSetBankH(VRAM_H_LCD); // Enable CPU access to VRAM_H
u32 address = 0x06898000 + (layer << 13) + (color * 256 * 2); // First color
*((u16*)address) = (u16)0xFF00FF;
address = 0x06898000 + (layer << 13) + (color * 256 * 2) + 2; // Second color
*((u16*)address) = rgb;
vramSetBankH(VRAM_H_SUB_BG_EXT_PALETTE);
}
}