mirror of
https://github.com/archr-linux/Arch-R.git
synced 2026-03-31 14:41:55 -07:00
Merge pull request #2054 from r3claimer/next
SDL2Text, filter out unrecognized chars
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include <dirent.h>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <cstdint>
|
||||
|
||||
// -------------------- Config Helpers --------------------
|
||||
std::filesystem::path get_config_file() {
|
||||
@@ -19,7 +20,7 @@ std::filesystem::path get_config_file() {
|
||||
return cfgDir / "sdl2text.conf";
|
||||
}
|
||||
|
||||
struct FileConfig { int scroll = 0; int fontSize = 40; }; // default font size 32
|
||||
struct FileConfig { int scroll = 0; int fontSize = 40; }; // default font size 40
|
||||
|
||||
std::map<std::string, FileConfig> load_config() {
|
||||
std::map<std::string, FileConfig> cfg;
|
||||
@@ -33,10 +34,12 @@ std::map<std::string, FileConfig> load_config() {
|
||||
std::string val = line.substr(sep+1);
|
||||
size_t comma = val.find(',');
|
||||
int scroll = 0;
|
||||
int font = 32;
|
||||
int font = 40;
|
||||
if(comma != std::string::npos){
|
||||
scroll = std::stoi(val.substr(0,comma));
|
||||
font = std::stoi(val.substr(comma+1));
|
||||
try {
|
||||
scroll = std::stoi(val.substr(0,comma));
|
||||
font = std::stoi(val.substr(comma+1));
|
||||
} catch(...) { /* ignore parse errors, use defaults */ }
|
||||
}
|
||||
cfg[key] = {scroll,font};
|
||||
}
|
||||
@@ -85,7 +88,64 @@ std::string find_any_ttf_font() {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Split long line into chunks to fit screen width
|
||||
// -------------------- UTF-8 safe filtering for unsupported glyphs --------------------
|
||||
static bool font_can_render_codepoint(TTF_Font* font, uint32_t cp) {
|
||||
if (cp <= 0xFFFF) {
|
||||
return TTF_GlyphIsProvided(font, static_cast<Uint16>(cp)) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string filter_invalid_chars(const std::string& s, TTF_Font* font) {
|
||||
std::string out;
|
||||
size_t i = 0;
|
||||
while (i < s.size()) {
|
||||
unsigned char c = static_cast<unsigned char>(s[i]);
|
||||
if (c < 0x80) {
|
||||
if (font_can_render_codepoint(font, c)) out.push_back(static_cast<char>(c));
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t cp = 0;
|
||||
size_t seqLen = 0;
|
||||
|
||||
if ((c & 0xE0) == 0xC0) {
|
||||
if (i + 1 >= s.size()) { ++i; continue; }
|
||||
unsigned char c1 = static_cast<unsigned char>(s[i+1]);
|
||||
if ((c1 & 0xC0) != 0x80) { ++i; continue; }
|
||||
cp = ((c & 0x1F) << 6) | (c1 & 0x3F);
|
||||
seqLen = 2;
|
||||
if (cp < 0x80) { i += 2; continue; }
|
||||
} else if ((c & 0xF0) == 0xE0) {
|
||||
if (i + 2 >= s.size()) { ++i; continue; }
|
||||
unsigned char c1 = static_cast<unsigned char>(s[i+1]);
|
||||
unsigned char c2 = static_cast<unsigned char>(s[i+2]);
|
||||
if ((c1 & 0xC0) != 0x80 || (c2 & 0xC0) != 0x80) { ++i; continue; }
|
||||
cp = ((c & 0x0F) << 12) | ((c1 & 0x3F) << 6) | (c2 & 0x3F);
|
||||
seqLen = 3;
|
||||
} else if ((c & 0xF8) == 0xF0) {
|
||||
if (i + 3 >= s.size()) { ++i; continue; }
|
||||
unsigned char c1 = static_cast<unsigned char>(s[i+1]);
|
||||
unsigned char c2 = static_cast<unsigned char>(s[i+2]);
|
||||
unsigned char c3 = static_cast<unsigned char>(s[i+3]);
|
||||
if ((c1 & 0xC0) != 0x80 || (c2 & 0xC0) != 0x80 || (c3 & 0xC0) != 0x80) { ++i; continue; }
|
||||
cp = ((c & 0x07) << 18) | ((c1 & 0x3F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
|
||||
seqLen = 4;
|
||||
} else { ++i; continue; }
|
||||
|
||||
if (seqLen == 0) { ++i; continue; }
|
||||
|
||||
if (font_can_render_codepoint(font, cp)) {
|
||||
out.append(s.substr(i, seqLen));
|
||||
}
|
||||
|
||||
i += seqLen;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// -------------------- Split long line into chunks to fit screen width --------------------
|
||||
std::vector<std::string> wrap_line(const std::string& line, TTF_Font* font, int maxWidth) {
|
||||
std::vector<std::string> result;
|
||||
size_t start = 0;
|
||||
@@ -136,15 +196,22 @@ int main(int argc, char* argv[]) {
|
||||
auto lines = load_file_lines(textFile);
|
||||
if(lines.empty()){ std::cout << "Failed to load file\n"; return 1; }
|
||||
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER);
|
||||
TTF_Init();
|
||||
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) != 0) {
|
||||
std::cerr << "SDL_Init error: " << SDL_GetError() << "\n";
|
||||
return 1;
|
||||
}
|
||||
if (TTF_Init() != 0) {
|
||||
std::cerr << "TTF_Init error: " << TTF_GetError() << "\n";
|
||||
SDL_Quit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string fontPath = find_any_ttf_font();
|
||||
if(fontPath.empty()){ std::cout << "No TTF font found\n"; return 1; }
|
||||
if(fontPath.empty()){ std::cout << "No TTF font found\n"; TTF_Quit(); SDL_Quit(); return 1; }
|
||||
std::cout << "Using font: " << fontPath << "\n";
|
||||
|
||||
auto cfg = load_config();
|
||||
FileConfig fcfg = cfg[textFile]; // default scroll=0, fontSize=32
|
||||
FileConfig fcfg = cfg[textFile];
|
||||
int fontSize = fcfg.fontSize;
|
||||
|
||||
auto loadFont = [&](int size) -> TTF_Font* {
|
||||
@@ -153,14 +220,17 @@ int main(int argc, char* argv[]) {
|
||||
return f;
|
||||
};
|
||||
TTF_Font* font = loadFont(fontSize);
|
||||
if (!font) { TTF_Quit(); SDL_Quit(); return 1; }
|
||||
|
||||
SDL_Window* win = SDL_CreateWindow("Text Viewer",
|
||||
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
0,0,
|
||||
SDL_WINDOW_FULLSCREEN_DESKTOP|SDL_WINDOW_BORDERLESS);
|
||||
if (!win) { std::cerr << "SDL_CreateWindow error\n"; TTF_CloseFont(font); TTF_Quit(); SDL_Quit(); return 1; }
|
||||
SDL_Renderer* ren = SDL_CreateRenderer(win,-1,SDL_RENDERER_ACCELERATED);
|
||||
if (!ren) { SDL_DestroyWindow(win); TTF_CloseFont(font); TTF_Quit(); SDL_Quit(); return 1; }
|
||||
|
||||
int WINDOW_W, WINDOW_H;
|
||||
int WINDOW_W = 0, WINDOW_H = 0;
|
||||
SDL_GetWindowSize(win,&WINDOW_W,&WINDOW_H);
|
||||
SDL_Color white = {255,255,255,255};
|
||||
|
||||
@@ -171,12 +241,17 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
SDL_GameController* pad = nullptr;
|
||||
for(int i=0;i<SDL_NumJoysticks();i++){
|
||||
if(SDL_IsGameController(i)){ pad=SDL_GameControllerOpen(i); break; }
|
||||
if(SDL_IsGameController(i)){
|
||||
pad = SDL_GameControllerOpen(i);
|
||||
if (pad) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap and pre-render
|
||||
std::vector<std::string> wrapped;
|
||||
for(auto &line: lines){
|
||||
auto chunks = wrap_line(line,font,WINDOW_W-20);
|
||||
std::string clean = filter_invalid_chars(line, font);
|
||||
auto chunks = wrap_line(clean,font,WINDOW_W-20);
|
||||
wrapped.insert(wrapped.end(),chunks.begin(),chunks.end());
|
||||
}
|
||||
auto textures = create_textures(ren, font, wrapped, white);
|
||||
@@ -185,45 +260,64 @@ int main(int argc, char* argv[]) {
|
||||
bool running=true;
|
||||
SDL_Event e;
|
||||
|
||||
// Touch drag-only state
|
||||
bool touchActive = false;
|
||||
float lastTouchY = 0.0f;
|
||||
const float TOUCH_MULTIPLIER = 1.75f;
|
||||
|
||||
// Help overlay
|
||||
bool showHelp = false;
|
||||
SDL_Color helpColor = {255,255,0,255};
|
||||
SDL_Color boxColor = {0,0,0,200};
|
||||
|
||||
while(running){
|
||||
while(SDL_PollEvent(&e)){
|
||||
if(e.type==SDL_QUIT) running=false;
|
||||
if(e.type==SDL_QUIT) { running=false; break; }
|
||||
|
||||
// Controller input
|
||||
if(e.type==SDL_CONTROLLERBUTTONDOWN){
|
||||
switch(e.cbutton.button){
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_UP: upPressed=true; break;
|
||||
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: downPressed=true; break;
|
||||
case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: l1Pressed=true; break;
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: r1Pressed=true; break;
|
||||
case SDL_CONTROLLER_BUTTON_Y: // North - increase font
|
||||
case SDL_CONTROLLER_BUTTON_X:
|
||||
fontSize += 2;
|
||||
TTF_CloseFont(font);
|
||||
font = loadFont(fontSize);
|
||||
if (!font) { fontSize -= 2; font = loadFont(fontSize); }
|
||||
wrapped.clear();
|
||||
for(auto &line: lines){
|
||||
auto chunks = wrap_line(line,font,WINDOW_W-20);
|
||||
std::string clean = filter_invalid_chars(line, font);
|
||||
auto chunks = wrap_line(clean,font,WINDOW_W-20);
|
||||
wrapped.insert(wrapped.end(),chunks.begin(),chunks.end());
|
||||
}
|
||||
for(auto <: textures) SDL_DestroyTexture(lt.tex);
|
||||
for(auto <: textures) if (lt.tex) SDL_DestroyTexture(lt.tex);
|
||||
textures = create_textures(ren, font, wrapped, white);
|
||||
lineHeight = TTF_FontHeight(font);
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_X: // West - decrease font
|
||||
case SDL_CONTROLLER_BUTTON_Y:
|
||||
fontSize -= 2;
|
||||
if(fontSize<8) fontSize=8;
|
||||
TTF_CloseFont(font);
|
||||
font = loadFont(fontSize);
|
||||
if (!font) { fontSize += 2; font = loadFont(fontSize); }
|
||||
wrapped.clear();
|
||||
for(auto &line: lines){
|
||||
auto chunks = wrap_line(line,font,WINDOW_W-20);
|
||||
std::string clean = filter_invalid_chars(line, font);
|
||||
auto chunks = wrap_line(clean,font,WINDOW_W-20);
|
||||
wrapped.insert(wrapped.end(),chunks.begin(),chunks.end());
|
||||
}
|
||||
for(auto <: textures) SDL_DestroyTexture(lt.tex);
|
||||
for(auto <: textures) if (lt.tex) SDL_DestroyTexture(lt.tex);
|
||||
textures = create_textures(ren, font, wrapped, white);
|
||||
lineHeight = TTF_FontHeight(font);
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_B: // South - close
|
||||
case SDL_CONTROLLER_BUTTON_B:
|
||||
running=false;
|
||||
break;
|
||||
case SDL_CONTROLLER_BUTTON_BACK: // SELECT button
|
||||
showHelp = !showHelp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(e.type==SDL_CONTROLLERBUTTONUP){
|
||||
@@ -234,55 +328,109 @@ int main(int argc, char* argv[]) {
|
||||
case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: r1Pressed=false; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Touch events
|
||||
if(e.type == SDL_FINGERDOWN) {
|
||||
touchActive = true;
|
||||
lastTouchY = e.tfinger.y;
|
||||
}
|
||||
if(e.type == SDL_FINGERMOTION && touchActive) {
|
||||
float y = e.tfinger.y;
|
||||
float dy_norm = y - lastTouchY;
|
||||
lastTouchY = y;
|
||||
int deltaPixels = (int)(-dy_norm * WINDOW_H * TOUCH_MULTIPLIER);
|
||||
scroll_y += deltaPixels;
|
||||
}
|
||||
if(e.type == SDL_FINGERUP) touchActive = false;
|
||||
}
|
||||
|
||||
// scrolling
|
||||
// Controller continuous scrolling
|
||||
if(upPressed) scroll_y -= SCROLL_SPEED;
|
||||
if(downPressed) scroll_y += SCROLL_SPEED;
|
||||
if(l1Pressed) scroll_y -= SKIP_LINES*lineHeight;
|
||||
if(r1Pressed) scroll_y += SKIP_LINES*lineHeight;
|
||||
|
||||
// Clamp
|
||||
int total_height = (int)textures.size()*lineHeight;
|
||||
if(total_height < WINDOW_H) total_height = WINDOW_H;
|
||||
if(scroll_y < 0) scroll_y=0;
|
||||
if(scroll_y > total_height-WINDOW_H) scroll_y = total_height-WINDOW_H;
|
||||
|
||||
// render
|
||||
// Render
|
||||
SDL_SetRenderDrawColor(ren,0,0,0,255);
|
||||
SDL_RenderClear(ren);
|
||||
|
||||
int firstLine = scroll_y/lineHeight;
|
||||
int offsetY = -(scroll_y%lineHeight);
|
||||
for(size_t i=firstLine; i<textures.size() && offsetY<WINDOW_H; i++){
|
||||
SDL_Rect dst = {10, offsetY, textures[i].w, textures[i].h};
|
||||
SDL_RenderCopy(ren, textures[i].tex, nullptr, &dst);
|
||||
if (textures[i].tex) {
|
||||
SDL_Rect dst = {10, offsetY, textures[i].w, textures[i].h};
|
||||
SDL_RenderCopy(ren, textures[i].tex, nullptr, &dst);
|
||||
}
|
||||
offsetY += textures[i].h;
|
||||
}
|
||||
|
||||
// scroll bar
|
||||
// Scroll bar
|
||||
int barWidth = 8;
|
||||
float scrollPercent = (float)scroll_y / (float)(total_height - WINDOW_H);
|
||||
if(scrollPercent < 0) { scrollPercent = 0; }
|
||||
if(scrollPercent > 1) { scrollPercent = 1; }
|
||||
int barHeight = (int)((float)WINDOW_H * (float)WINDOW_H / total_height);
|
||||
float scrollPercent = 0.0f;
|
||||
if (total_height > WINDOW_H) scrollPercent = (float)scroll_y / (float)(total_height - WINDOW_H);
|
||||
if(scrollPercent < 0) scrollPercent = 0;
|
||||
if(scrollPercent > 1) scrollPercent = 1;
|
||||
int barHeight = (int)((float)WINDOW_H * (float)WINDOW_H / (float)total_height);
|
||||
if(barHeight < 10) barHeight = 10;
|
||||
int barY = (int)(scrollPercent*(WINDOW_H - barHeight));
|
||||
SDL_Rect scrollbar = {WINDOW_W - barWidth - 2, barY, barWidth, barHeight};
|
||||
SDL_SetRenderDrawColor(ren, 200,200,200,255);
|
||||
SDL_RenderFillRect(ren, &scrollbar);
|
||||
|
||||
// Help overlay
|
||||
if(showHelp) {
|
||||
int boxW = WINDOW_W / 2;
|
||||
int boxH = WINDOW_H / 2;
|
||||
int boxX = (WINDOW_W - boxW)/2;
|
||||
int boxY = (WINDOW_H - boxH)/2;
|
||||
|
||||
SDL_Rect helpBox = {boxX, boxY, boxW, boxH};
|
||||
SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(ren, boxColor.r, boxColor.g, boxColor.b, boxColor.a);
|
||||
SDL_RenderFillRect(ren, &helpBox);
|
||||
|
||||
std::vector<std::string> helpLines = {
|
||||
"CONTROLS:",
|
||||
"UP/DOWN DPAD - Scroll",
|
||||
"L1/R1 - Skip lines",
|
||||
"X/Y - Increase/Decrease font",
|
||||
"B - Exit",
|
||||
"SELECT - Toggle this help",
|
||||
"Touch Drag - Scroll"
|
||||
};
|
||||
|
||||
int ty = boxY + 20;
|
||||
for(auto &line : helpLines){
|
||||
SDL_Surface* surf = TTF_RenderUTF8_Blended(font, line.c_str(), helpColor);
|
||||
if(!surf) continue;
|
||||
SDL_Texture* tex = SDL_CreateTextureFromSurface(ren, surf);
|
||||
SDL_Rect dst = {boxX + 20, ty, surf->w, surf->h};
|
||||
SDL_RenderCopy(ren, tex, nullptr, &dst);
|
||||
ty += surf->h + 8;
|
||||
SDL_DestroyTexture(tex);
|
||||
SDL_FreeSurface(surf);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(ren);
|
||||
SDL_Delay(16);
|
||||
}
|
||||
|
||||
// save scroll and fontSize using full path
|
||||
// Save config
|
||||
cfg[textFile] = {scroll_y, fontSize};
|
||||
save_config(cfg);
|
||||
|
||||
for(auto <: textures) SDL_DestroyTexture(lt.tex);
|
||||
for(auto <: textures) if (lt.tex) SDL_DestroyTexture(lt.tex);
|
||||
if(pad) SDL_GameControllerClose(pad);
|
||||
TTF_CloseFont(font);
|
||||
SDL_DestroyRenderer(ren);
|
||||
SDL_DestroyWindow(win);
|
||||
if(font) TTF_CloseFont(font);
|
||||
if(ren) SDL_DestroyRenderer(ren);
|
||||
if(win) SDL_DestroyWindow(win);
|
||||
TTF_Quit();
|
||||
SDL_Quit();
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user