mirror of
https://github.com/knightfox75/nds_nflib.git
synced 2025-06-18 16:55:32 -04:00

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).
444 lines
13 KiB
C
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);
|
|
}
|
|
}
|