nds_nflib/source/nf_text.c
Antonio Niño Díaz e1506cc74f library: Cleanup of types
Types like u8 and u16 aren't always a good idea. For example:

    void function(u8 x, u8 y)
    {
        u32 value = (x << 16) | y;
    }

The left shift of x will overflow because x is 8 bits wide. It is better
to make both arguments 32 bit wide.

It may also cause the compiler to introduce bit masking operations at
the caller side because the caller doesn't know how the function
behaves internally.

In order to prevent this kind of issues, it's better to use 32 bit
variables unless there is a very good reason to use smaller types (like
in structs, to save RAM).
2023-05-24 02:53:36 +01:00

444 lines
13 KiB
C

// SPDX-License-Identifier: MIT
//
// Copyright (c) 2009-2014 Cesar Rincon "NightFox"
//
// NightFox LIB - Funciones de Textos
// 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 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
NF_Error(101, filename, 0);
// Get file size
NF_TILEDBG[slot].tilesize = NF_TEXT_FONT_CHARS << 6; // 100 chars x 64 bytes
// 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
// (width / 8) * (height / 8) * 2
NF_TILEDBG[slot].mapsize = ((width >> 3) * (height >> 3)) << 1;
// 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);
file_id = fopen(filename, "rb");
if (file_id == NULL) // If the file can't be opened
NF_Error(101, filename, 0);
// Get file size
fseek(file_id, 0, SEEK_END);
NF_TILEDBG[slot].palsize = ftell(file_id);
rewind(file_id);
// Allocate space in RAM
NF_BUFFER_BGPAL[slot] = malloc(NF_TILEDBG[slot].palsize);
if (NF_BUFFER_BGPAL[slot] == NULL) // If there isn't enough free RAM
NF_Error(102, NULL, NF_TILEDBG[slot].palsize);
// Read file to RAM
fread(NF_BUFFER_BGPAL[slot], 1, NF_TILEDBG[slot].palsize, file_id);
fclose(file_id);
// 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 = text[n] - 32; // Skip the first 32 non-printable characters
if (value < 0)
value = 0;
string[n] = value;
// Handle special characters
if (string[n] > 95)
{
switch (text[n])
{
case 10: // \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
int 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
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);
}
}