kobodl/graphics/sofont.cpp
Ville Lindholm dbc223eb84
Initial commit
Import existing source tree; original VCS history is no longer available.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-28 16:35:31 +03:00

578 lines
11 KiB
C++

#include "logger.h"
#include "stdlib.h"
#include "sofont.h"
#include "string.h"
SoFont::SoFont()
{
picture = NULL;
CharPos = NULL;
CharOffset = NULL;
Spacing = NULL;
height = 0;
max_i = 0;
spacew = 0;
cursShift = 0;
background = 0;
xspace = 1;
}
SoFont::~SoFont()
{
if(picture)
SDL_FreeSurface(picture);
delete[]CharPos;
delete[]CharOffset;
delete[]Spacing;
}
namespace SoFontUtilities
{
Uint32 SoFontGetPixel(SDL_Surface * Surface, Sint32 X, Sint32 Y)
{
Uint8 *bits;
Uint32 Bpp;
if(!Surface)
{
log_printf(ELOG, "SoFontGetPixel: No surface!\n");
return 0;
}
if((X < 0) || (X >= Surface->w))
{
log_printf(ELOG, "SoFontGetPixel: X (%d)"
" out of range!\n", X);
return 0;
}
Bpp = Surface->format->BytesPerPixel;
bits = ((Uint8 *) Surface->pixels) + Y * Surface->pitch +
X * Bpp;
switch (Bpp)
{
case 1:
return *((Uint8 *) Surface->pixels +
Y * Surface->pitch + X);
break;
case 2:
return *((Uint16 *) Surface->pixels +
Y * Surface->pitch / 2 + X);
break;
case 3:
// Format/endian independent
Uint8 r, g, b;
r = *((bits) + Surface->format->Rshift / 8);
g = *((bits) + Surface->format->Gshift / 8);
b = *((bits) + Surface->format->Bshift / 8);
return SDL_MapRGB(Surface->format, r, g, b);
break;
case 4:
return *((Uint32 *) Surface->pixels +
Y * Surface->pitch / 4 + X);
break;
}
log_printf(ELOG, "SoFontGetPixel: Unsupported pixel format!\n");
return 0; // David (to get rid of warning)
}
void SoFontSetPixel(SDL_Surface * Surface, Sint32 X, Sint32 Y,
Uint32 c)
{
Uint8 *bits;
Uint32 Bpp;
if(!Surface)
{
log_printf(ELOG, "SoFontSetPixel: No surface!\n");
return;
}
if((X < 0) || (X >= Surface->w))
{
log_printf(ELOG, "SoFontSetPixel: X (%d)"
" out of range!\n", X);
return;
}
Bpp = Surface->format->BytesPerPixel;
bits = ((Uint8 *) Surface->pixels) + Y * Surface->pitch +
X * Bpp;
switch (Bpp)
{
case 1:
*((Uint8 *) Surface->pixels + Y * Surface->pitch +
X) = (Uint8) c;
break;
case 2:
*((Uint16 *) Surface->pixels +
Y * Surface->pitch / 2 + X) =
(Uint16) c;
break;
case 3:
// Format/endian independent
Uint8 r, g, b;
SDL_GetRGB(c, Surface->format, &r, &g, &b);
*((bits) + Surface->format->Rshift / 8) = r;
*((bits) + Surface->format->Gshift / 8) = g;
*((bits) + Surface->format->Bshift / 8) = b;
break;
case 4:
*((Uint32 *) Surface->pixels +
Y * Surface->pitch / 4 + X) = c;
break;
}
}
void clipx(SDL_Rect * srcrect, SDL_Rect * dstrect, SDL_Rect * clip)
{
// Use if destination have the same size than source.
int dx = clip->x - dstrect->x;
int sw = srcrect->w; // Because SDL_Rect.w are
// unsigned.
int dw = dstrect->w;
if(dx > 0)
{
srcrect->x += dx;
dstrect->x += dx;
sw -= dx;
dw -= dx;
}
int dwx = (dstrect->x + dstrect->w) - (clip->x + clip->w);
if(dwx > 0)
{
sw -= dwx;
dw -= dwx;
}
if(sw > 0)
srcrect->w = sw;
else
srcrect->w = 0;
if(dw > 0)
dstrect->w = dw;
else
dstrect->w = 0;
}
void sdcRects(SDL_Rect * source, SDL_Rect * destination,
SDL_Rect clipping)
{
// Use if destination have the same size than source &
// cliping on destination
int dx = clipping.x - destination->x;
int dy = clipping.y - destination->y;
int sw = source->w;
int sh = source->h;
if(dx > 0)
{
source->x += dx;
destination->x += dx;
sw -= dx;
destination->w -= dx;
}
if(dy > 0)
{
source->y += dy;
destination->y += dy;
sh -= dy;
destination->h -= dy;
}
int dwx = (destination->x + destination->w) - (clipping.x +
clipping.w);
int dhy = (destination->y + destination->h) - (clipping.y +
clipping.h);
if(dwx > 0)
{
sw -= dwx;
destination->w -= dwx;
}
if(dhy > 0)
{
sh -= dhy;
destination->h -= dhy;
}
if(sw > 0)
source->w = sw;
else
source->w = 0;
if(sh > 0)
source->h = sh;
else
source->h = 0;
}
}
using namespace SoFontUtilities;
bool SoFont::DoStartNewChar(Sint32 x)
{
if(!picture)
return false;
return SoFontGetPixel(picture, x, 0) ==
SDL_MapRGB(picture->format, 255, 0, 255);
}
void SoFont::CleanSurface()
{
if(!picture)
return;
int x = 0, y = 0;
Uint32 pix = SDL_MapRGB(picture->format, 255, 0, 255);
while(x < picture->w)
{
y = 0;
if(SoFontGetPixel(picture, x, y) == pix)
SoFontSetPixel(picture, x, y, background);
x++;
}
}
bool SoFont::load(SDL_Surface * FontSurface)
{
int x = 0, i = 0, s = 0;
int _CharPos[256];
int _CharOffset[256];
int _Spacing[256];
if(!FontSurface)
{
log_printf(ELOG, "SoFont recieved a NULL SDL_Surface\n");
return false;
}
if(picture)
SDL_FreeSurface(picture);
picture = FontSurface;
height = picture->h - 1;
while(x < picture->w)
{
if(DoStartNewChar(x))
{
if(i)
_Spacing[i - 1] = x - s + xspace;
int p = x;
while((x < picture->w) && (DoStartNewChar(x)))
x++;
s = x;
_CharPos[i] = (x + p + 1) / 2;
_CharOffset[i] = x - _CharPos[i];
i++;
}
x++;
}
//Note that spacing is not needed for the last char,
//as it's just used for the blit width calculation.
if(i)
_Spacing[i - 1] = x - s;
_Spacing[i] = 0;
_CharOffset[i] = 0;
_CharPos[i++] = picture->w;
max_i = START_CHAR + i - 1;
background = SoFontGetPixel(picture, 0, height);
// FIXME: Use this when there is no alpha channel?
// SDL_SetColorKey(picture, SDL_SRCCOLORKEY, background);
CleanSurface();
delete[]CharPos;
delete[]CharOffset;
delete[]Spacing;
CharPos = new int[i];
CharOffset = new int[i];
Spacing = new int[i];
memcpy(CharPos, _CharPos, i * sizeof(int));
memcpy(CharOffset, _CharOffset, i * sizeof(int));
memcpy(Spacing, _Spacing, i * sizeof(int));
// We search for a smart space width:
// Changed from "a", "A", "0" for Kobo Deluxe.
// Spaces were *way* to wide! //David
spacew = 0;
if(!spacew)
spacew = TextWidth("i") * 3 / 2;
if(!spacew)
spacew = TextWidth("I") * 3 / 2;
if(!spacew)
spacew = TextWidth(".") * 3 / 2;
if(!spacew)
spacew = CharPos[1] - CharPos[0];
// We search for a smart cursor position:
int cursBegin = 0;
int cursWidth = 0;
cursShift = 0;
if('|' > max_i)
return true; //No bar in this font!
if(background == SoFontGetPixel(picture, CharPos['|' - START_CHAR],
height / 2))
{
// Up to the first | color
for(cursBegin = 0; cursBegin <= TextWidth("|");
cursBegin++)
if(background != SoFontGetPixel(picture,
CharPos['|' - START_CHAR] +
cursBegin, height / 2))
break;
// Up to the end of the | color
for(cursWidth = 0; cursWidth <= TextWidth("|");
cursWidth++)
if(background == SoFontGetPixel(picture,
CharPos['|' - START_CHAR] +
cursBegin + cursWidth,
height / 2))
break;
}
else
{
// Up to the end of the | color
for(cursWidth = 0; cursWidth <= TextWidth("|");
cursWidth++)
if(background == SoFontGetPixel(picture,
CharPos['|' - START_CHAR] +
cursWidth, height / 2))
break;
}
cursShift = cursBegin + 1; // cursWidth could be used if
// image format changes.
return true;
}
void SoFont::PutString(SDL_Surface *Surface, int x, int y,
const char *text, SDL_Rect *clip)
{
if((!picture) || (!Surface) || (!text))
return;
int ofs, i = 0;
SDL_Rect srcrect, dstrect;
while(text[i] != '\0')
{
if(text[i] == ' ')
{
x += spacew;
i++;
}
else if((text[i] >= START_CHAR) && (text[i] <= max_i))
{
ofs = text[i] - START_CHAR;
srcrect.w = dstrect.w =
this->CharPos[ofs + 1] -
this->CharPos[ofs];
srcrect.h = dstrect.h = height;
srcrect.x = this->CharPos[ofs];
srcrect.y = 1;
dstrect.x = x - CharOffset[ofs];
dstrect.y = y;
x += Spacing[ofs];
if(clip)
sdcRects(&srcrect, &dstrect, *clip);
SDL_BlitSurface(picture, &srcrect, Surface,
&dstrect);
i++;
}
else
i++; // other chars are ignored
// Coarse clipping
if(clip)
{
if(x > clip->x + clip->w)
return;
}
else
{
if(x > Surface->w)
return;
}
}
}
void SoFont::PutStringWithCursor(SDL_Surface * Surface, int xs, int y,
const char *text, int cursPos, SDL_Rect * clip,
bool showCurs)
{
if((!picture) || (!Surface) || (!text))
return;
if('|' > max_i)
showCurs = false;
int ofs, i = 0, x = xs;
SDL_Rect srcrect, dstrect;
// We want the cursor to appear under the main text.
if(showCurs)
{
while(text[i] != '\0')
if(i == cursPos)
break;
else if(text[i] == ' ')
{
x += spacew;
i++;
}
else if((text[i] >= START_CHAR)
&& (text[i] <= max_i))
{
ofs = text[i] - START_CHAR;
x += Spacing[ofs];
i++;
}
else
i++;
ofs = '|' - START_CHAR;
srcrect.w = dstrect.w = CharPos[ofs + 1] - CharPos[ofs];
srcrect.h = dstrect.h = height;
srcrect.x = this->CharPos[ofs];
srcrect.y = 1;
dstrect.x = x - cursShift;
dstrect.y = y;
if(clip)
sdcRects(&srcrect, &dstrect, *clip);
SDL_BlitSurface(picture, &srcrect, Surface, &dstrect);
}
// Then the text:
PutString(Surface, xs, y, text, clip);
}
int SoFont::TextWidth(const char *text, int min, int max)
{
if(!picture)
return 0;
int ofs, x = 0, i = min;
while((text[i] != '\0') && (i < max))
{
if(text[i] == ' ')
{
x += spacew;
i++;
}
else if((text[i] >= START_CHAR) && (text[i] <= max_i))
{
ofs = text[i] - START_CHAR;
x += Spacing[ofs];
i++;
}
else
i++;
}
return x;
}
void SoFont::XCenteredString(SDL_Surface * Surface, int y,
const char *text, SDL_Rect * clip)
{
if(!picture)
return;
PutString(Surface, Surface->w / 2 - TextWidth(text) / 2, y, text,
clip);
}
void SoFont::CenteredString(SDL_Surface * Surface, int x, int y,
const char *text, SDL_Rect * clip)
{
if(!picture)
return;
PutString(Surface, x - TextWidth(text) / 2, y - height / 2, text,
clip);
}
void SoFont::CenteredString(SDL_Surface * Surface, const char *text,
SDL_Rect * clip)
{
if(!picture)
return;
CenteredString(Surface, Surface->clip_rect.w / 2,
Surface->clip_rect.h / 2, text, clip);
}
void SoFont::PutStringCleverCursor(SDL_Surface * Surface, const char *text,
int cursPos, SDL_Rect * r, SDL_Rect * clip, bool showCurs)
{
if((!picture) || (!text))
return;
int w1, w2;
w1 = TextWidth(text, 0, cursPos);
w2 = TextWidth(text);
if((w2 < r->w) || (w1 < r->w / 2))
PutStringWithCursor(Surface, r->x,
r->y + (r->h - height) / 2, text, cursPos,
clip, showCurs);
else if(w1 < w2 - r->w / 2)
PutStringWithCursor(Surface, r->x - w1 + r->w / 2,
r->y + (r->h - height) / 2, text, cursPos,
clip, showCurs);
else
PutStringWithCursor(Surface, r->x - w2 + r->w,
r->y + (r->h - height) / 2, text, cursPos,
clip, showCurs);
}
int SoFont::TextCursorAt(const char *text, int px)
{
if(!picture)
return 0;
int ofs, x = 0, i = 0, ax = 0;
if(px <= 0)
return 0;
while(text[i] != '\0')
{
if(text[i] == ' ')
{
x += spacew;
i++;
}
else if((text[i] >= START_CHAR) && (text[i] <= max_i))
{
ofs = text[i] - START_CHAR;
x += Spacing[ofs];
i++;
}
else
i++;
if(px < (ax + x) / 2)
return (i - 1);
ax = x;
}
return i;
}
int SoFont::CleverTextCursorAt(const char *text, int px, int cursPos,
SDL_Rect * r)
{
if((!picture) || (!text))
return 0;
int w1, w2;
w1 = TextWidth(text, 0, cursPos);
w2 = TextWidth(text);
if((w2 < r->w) || (w1 < r->w / 2))
return TextCursorAt(text, px);
else if(w1 < w2 - r->w / 2)
return TextCursorAt(text, px + w1 - (r->w / 2));
else
return TextCursorAt(text, px + w2 - r->w);
}