Completes phases 3-6 of the migration plan. After this commit, the
codebase compiles and links against pure SDL 3 (no compat shim). The
binary is unverified at runtime but the build is clean.
Renderer (graphics/gfxengine.{cpp,h}):
- Replace SDL_SetVideoMode + SDL_Flip + page-flip machinery with
SDL_CreateWindow + SDL_CreateRenderer + a streaming SDL_Texture.
The CPU-side back buffer (softbuf) is now always ARGB8888; legacy
HW/double-buffer/page flags are dead.
- flip() does a single full-frame SDL_UpdateTexture + SDL_RenderClear
+ SDL_RenderTexture + SDL_RenderPresent each frame. The dirtyrect
iteration is retained only to drive widget phys_refresh() callbacks.
- SDL_SetRenderLogicalPresentation gives us letterbox scaling for free.
- SDL_SetRenderVSync replaces glSDL_VSync.
- title()/cursor() ported to SDL 3 window/cursor APIs.
Surface API (graphics/sprite.c, filters.c, window.cpp, sofont.cpp,
region.c):
- SDL_FreeSurface → SDL_DestroySurface
- SDL_FillRect → SDL_FillSurfaceRect
- SDL_SetClipRect → SDL_SetSurfaceClipRect
- SDL_SetColorKey(s, flags, key) → SDL_SetSurfaceColorKey(s, bool, key)
- SDL_SetAlpha(s, flags, a) → SDL_SetSurfaceAlphaMod + BlendMode
- SDL_SetColors → SDL_SetSurfacePalette
- SDL_CreateRGBSurface(...) → SDL_CreateSurface(w, h, format_enum)
- SDL_DisplayFormat[Alpha] → SDL_ConvertSurface(s, ARGB8888)
- SDL_MapRGB(surf->format, ...) → SDL_MapSurfaceRGB(surf, ...)
- surface->format->Field → SDL_GetPixelFormatDetails(surf->format)->field
- surface->format->Amask test → SDL_ISPIXELFORMAT_ALPHA
- surface->clip_rect → SDL_GetSurfaceClipRect
Events (kobo.cpp):
- SDL_{KEYDOWN,KEYUP,QUIT,VIDEOEXPOSE,MOUSE*,JOY*} → SDL_EVENT_*
- SDL_ACTIVEEVENT (with gain check) → SDL_EVENT_WINDOW_FOCUS_LOST
- ev.key.keysym.sym → ev.key.key (.unicode → text input, deferred)
- SDLK_PRINT/SYSREQ → SDLK_PRINTSCREEN; lowercase SDLK_p/y/n → uppercase
- ev.motion.x/y for button events → ev.button.x/y (struct layout differs)
- KMOD_META → SDL_KMOD_GUI
- SDL_INIT_NOPARACHUTE → SDL_Init(0) (no parachute by default in SDL 3)
Keymap (gamectl.{cpp,h}):
- SDLKey → SDL_Keycode
- SDLK_KP{1..9} → SDLK_KP_{1..9}
Stubs deferred for later phases (binary will run but with degraded
behavior):
- sound/audio.c: _start_SDL_output returns -1 immediately; SDL 1.2
audio callback model needs full rewrite onto SDL_AudioStream.
- kobo.cpp init_js/close_js: SDL 3 joystick API uses instance IDs;
re-port deferred.
- states.cpp SDL_WM_GrabInput/SDL_EnableUNICODE: need window pointer
exposed from gfxengine; stubbed.
- gamectl.cpp SDL_EnableKeyRepeat: removed; relies on ev.key.repeat.
Other adjustments:
- options.cpp: GFX_DRIVER_GLSDL menu paths removed; show vsync option
always (renderer handles it).
- sound/a_wave.c: AUDIO_S8/AUDIO_S16SYS → SDL_AUDIO_S8/S16, SDL_FreeWAV
→ SDL_free.
- gfxengine.cpp: SDL_putenv("SDL_VIDEO_CENTERED=1") obsolete (handled
by SDL_CreateWindow defaults).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
589 lines
12 KiB
C++
589 lines
12 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_DestroySurface(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;
|
|
}
|
|
|
|
const SDL_PixelFormatDetails *fmt =
|
|
SDL_GetPixelFormatDetails(Surface->format);
|
|
if(!fmt)
|
|
return 0;
|
|
Bpp = fmt->bytes_per_pixel;
|
|
|
|
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) + fmt->Rshift / 8);
|
|
g = *((bits) + fmt->Gshift / 8);
|
|
b = *((bits) + fmt->Bshift / 8);
|
|
return SDL_MapSurfaceRGB(Surface, 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;
|
|
}
|
|
|
|
const SDL_PixelFormatDetails *fmt =
|
|
SDL_GetPixelFormatDetails(Surface->format);
|
|
if(!fmt)
|
|
return;
|
|
Bpp = fmt->bytes_per_pixel;
|
|
|
|
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, fmt, SDL_GetSurfacePalette(Surface),
|
|
&r, &g, &b);
|
|
*((bits) + fmt->Rshift / 8) = r;
|
|
*((bits) + fmt->Gshift / 8) = g;
|
|
*((bits) + fmt->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_MapSurfaceRGB(picture, 255, 0, 255);
|
|
}
|
|
|
|
void SoFont::CleanSurface()
|
|
{
|
|
if(!picture)
|
|
return;
|
|
|
|
int x = 0, y = 0;
|
|
Uint32 pix = SDL_MapSurfaceRGB(picture, 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_DestroySurface(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;
|
|
SDL_Rect surf_clip;
|
|
SDL_GetSurfaceClipRect(Surface, &surf_clip);
|
|
CenteredString(Surface, surf_clip.w / 2,
|
|
surf_clip.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);
|
|
}
|