diff --git a/runtime/launcher/launcher.cpp b/runtime/launcher/launcher.cpp index 440e2f34..c00e08bb 100644 --- a/runtime/launcher/launcher.cpp +++ b/runtime/launcher/launcher.cpp @@ -1,7 +1,12 @@ -// launcher.cpp — SDL2/OpenGL launcher. No RmlUi. No FreeType. No dependencies -// beyond SDL2 + OpenGL 3.3 core + stb_image/png + stb_truetype (single headers). +// launcher.cpp — see launcher.h. RmlUi (HTML/CSS) front-end over SDL2 + GL3. +// +// Uses RmlUi's official SDL platform + GL3 renderer backends (lib/RmlUi/Backends). +// The base RenderInterface_GL3 is used directly (no SDL_image dependency) — the +// minimal launcher draws with CSS, not external bitmaps; image-rich polish +// is a later phase. #include "launcher.h" + #include "config_loader.h" #include "disc_identity.h" @@ -10,22 +15,23 @@ extern "C" { #include "psx_keybinds.h" } +#include +#include +#include +#include + +#include "RmlUi_Platform_SDL.h" +#include "RmlUi_Renderer_GL3.h" + #include "third_party/stb_image.h" -#define STB_TRUETYPE_IMPLEMENTATION -#include "third_party/stb_truetype.h" - -#define GL_GLEXT_PROTOTYPES #include -#include + #include -#include -#include #include -#include -#include #include #include +#include #include #include @@ -35,651 +41,592 @@ extern "C" { #endif namespace fs = std::filesystem; + namespace { -// ---- math helpers ----------------------------------------------------------- -struct Color { float r,g,b,a; constexpr Color(float R,float G,float B,float A=1):r(R),g(G),b(B),a(A){} constexpr bool operator==(const Color& o)const{return r==o.r&&g==o.g&&b==o.b&&a==o.a;} }; -struct Vec2 { float x,y; }; -static inline Color operator*(Color c, float s) { return {c.r*s,c.g*s,c.b*s,c.a}; } +// RenderInterface_GL3 only decodes uncompressed TGA. Override LoadTexture to +// decode PNG via stb_image (falling back to the base TGA path), so the launcher +// can use with PNG art. RmlUi textures are premultiplied-alpha RGBA. +class LauncherRenderInterface : public RenderInterface_GL3 { +public: + Rml::TextureHandle LoadTexture(Rml::Vector2i& dims, const Rml::String& source) override { + Rml::FileInterface* fi = Rml::GetFileInterface(); + Rml::FileHandle fh = fi ? fi->Open(source) : Rml::FileHandle(0); + if (!fh) return RenderInterface_GL3::LoadTexture(dims, source); + fi->Seek(fh, 0, SEEK_END); + const size_t sz = (size_t)fi->Tell(fh); + fi->Seek(fh, 0, SEEK_SET); + std::vector buf(sz); + fi->Read(buf.data(), sz, fh); + fi->Close(fh); -// ---- theme ------------------------------------------------------------------ -constexpr Color - THEME_BG {0.043f,0.055f,0.078f,1}, - THEME_PANEL {0.067f,0.086f,0.122f,1}, - THEME_BORDER {0.118f,0.153f,0.200f,1}, - THEME_BORDER_HL {0.145f,0.184f,0.251f,1}, - THEME_TEXT {0.902f,0.914f,0.937f,1}, - THEME_TEXT_DIM {0.490f,0.459f,0.565f,1}, - THEME_TEXT_MUTED {0.392f,0.431f,0.498f,1}, - THEME_ACCENT {0.267f,0.576f,0.965f,1}, - THEME_ACCENT_HL {0.400f,0.671f,0.992f,1}, - THEME_GREEN {0.247f,0.725f,0.314f,1}, - THEME_RED {0.973f,0.282f,0.282f,1}, - THEME_WARN {0.824f,0.600f,0.133f,1}, - THEME_BTN_BG {0.106f,0.137f,0.188f,1}, - THEME_BTN_HOVER {0.157f,0.200f,0.259f,1}, - THEME_SEG_BG {0.086f,0.114f,0.157f,1}, - THEME_SEG_ON {0.145f,0.388f,0.922f,1}, - THEME_DROPDOWN_BG {0.051f,0.074f,0.110f,1}; + int w = 0, h = 0, comp = 0; + unsigned char* px = stbi_load_from_memory(buf.data(), (int)sz, &w, &h, &comp, 4); + if (!px) return RenderInterface_GL3::LoadTexture(dims, source); // maybe a TGA -// ---- GL 2D rendering helpers ------------------------------------------------ -static GLuint s_vao=0, s_vbo=0, s_prog=0, s_white=0; -static GLint s_u_proj=-1, s_u_tex=-1, s_u_col=-1; -static float s_proj[16]; // orthographic matrix, row-major + const size_t n = (size_t)w * (size_t)h; + for (size_t i = 0; i < n; i++) { // straight -> premultiplied alpha + const unsigned a = px[i * 4 + 3]; + px[i * 4 + 0] = (unsigned char)(px[i * 4 + 0] * a / 255); + px[i * 4 + 1] = (unsigned char)(px[i * 4 + 1] * a / 255); + px[i * 4 + 2] = (unsigned char)(px[i * 4 + 2] * a / 255); + } + dims.x = w; dims.y = h; + Rml::TextureHandle th = GenerateTexture({px, n * 4}, dims); + stbi_image_free(px); + return th; + } +}; -static void gl_ortho(float l, float r, float b, float t) { - std::memset(s_proj,0,sizeof(s_proj)); - s_proj[0]=2/(r-l); s_proj[5]=2/(t-b); s_proj[10]=-1; s_proj[12]=-(r+l)/(r-l); s_proj[13]=-(t+b)/(t-b); s_proj[15]=1; -} - -static void gl_ensure_init() { - if (s_prog) return; - const char* vs="#version 330 core\nlayout(location=0)in vec2 aP;layout(location=1)in vec2 aU;uniform mat4 uP;out vec2 vU;void main(){gl_Position=uP*vec4(aP,0,1);vU=aU;}"; - const char* fs="#version 330 core\nin vec2 vU;uniform sampler2D uT;uniform vec4 uC;out vec4 oC;void main(){oC=texture(uT,vU)*uC;}"; - auto compile=[](GLuint t,const char*s){ - GLuint sh=glCreateShader(t); glShaderSource(sh,1,&s,nullptr); glCompileShader(sh); return sh; - }; - GLuint v=compile(GL_VERTEX_SHADER,vs), f=compile(GL_FRAGMENT_SHADER,fs); - s_prog=glCreateProgram(); glAttachShader(s_prog,v); glAttachShader(s_prog,f); - glLinkProgram(s_prog); glDeleteShader(v); glDeleteShader(f); - s_u_proj=glGetUniformLocation(s_prog,"uP"); s_u_tex=glGetUniformLocation(s_prog,"uT"); s_u_col=glGetUniformLocation(s_prog,"uC"); - glGenVertexArrays(1,&s_vao); glGenBuffers(1,&s_vbo); - glBindVertexArray(s_vao); glBindBuffer(GL_ARRAY_BUFFER,s_vbo); - glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,16,(void*)0); glEnableVertexAttribArray(0); - glVertexAttribPointer(1,2,GL_FLOAT,GL_FALSE,16,(void*)8); glEnableVertexAttribArray(1); - unsigned char wp[4]={255,255,255,255}; - glGenTextures(1,&s_white); glBindTexture(GL_TEXTURE_2D,s_white); - glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,1,1,0,GL_RGBA,GL_UNSIGNED_BYTE,wp); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); -} - -static void gl_draw_quad(float x,float y,float w,float h, GLuint tex, const Color& c, - float u0=0,float v0=0,float u1=1,float v1=1) { - gl_ensure_init(); - struct V{float x,y,u,v;} verts[4]={{x,y+h,u0,v1},{x,y,u0,v0},{x+w,y+h,u1,v1},{x+w,y,u1,v0}}; - glUseProgram(s_prog); - glUniformMatrix4fv(s_u_proj,1,GL_FALSE,s_proj); - glUniform1i(s_u_tex,0); glUniform4f(s_u_col,c.r,c.g,c.b,c.a); - glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,tex); - glBindBuffer(GL_ARRAY_BUFFER,s_vbo); - glBufferData(GL_ARRAY_BUFFER,sizeof(verts),verts,GL_DYNAMIC_DRAW); - glBindVertexArray(s_vao); - glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); - glDrawArrays(GL_TRIANGLE_STRIP,0,4); -} - -static void gl_rect(float x,float y,float w,float h, const Color& c) { gl_draw_quad(x,y,w,h,s_white,c); } - -// ---- stb_truetype font system ----------------------------------------------- -struct Font { - GLuint tex=0; int tw=512, th=512; - stbtt_bakedchar cdata[96]{}; // ASCII 32..127 - float baseline=0; - - bool build(const unsigned char* ttf, float px) { - tex=0; tw=th=512; - auto* buf=(unsigned char*)std::calloc(tw*th,1); - if (!buf) return false; - int r=stbtt_BakeFontBitmap(ttf,0,px,buf,tw,th,32,96,cdata); - if (r<=0) { std::free(buf); return false; } - baseline=px*0.8f; // approximate ascender - glGenTextures(1,&tex); glBindTexture(GL_TEXTURE_2D,tex); - glTexImage2D(GL_TEXTURE_2D,0,GL_RED,tw,th,0,GL_RED,GL_UNSIGNED_BYTE,buf); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); - GLint swz[]={GL_ONE,GL_ONE,GL_ONE,GL_RED}; - glTexParameteriv(GL_TEXTURE_2D,GL_TEXTURE_SWIZZLE_RGBA,swz); - std::free(buf); +// Route RmlUi's own diagnostics to stdout. The base SystemInterface logs via +// OutputDebugString on Windows (invisible to a normal console/redirect), which +// hides data-binding errors; surfacing them here keeps RML issues debuggable. +class LauncherSystemInterface : public SystemInterface_SDL { +public: + bool LogMessage(Rml::Log::Type type, const Rml::String& message) override { + // Surface problems (warnings/errors/asserts); skip routine info spam. + if (type == Rml::Log::LT_INFO || type == Rml::Log::LT_DEBUG) return true; + const char* tag = type == Rml::Log::LT_ERROR ? "error" + : type == Rml::Log::LT_ASSERT ? "assert" : "warning"; + std::fprintf(stdout, "launcher/rml %s: %s\n", tag, message.c_str()); + std::fflush(stdout); return true; } - - void text(float x, float y, const char* s, const Color& c, float scale=1) const { - if (!tex) return; - while (*s) { - if (*s>=32 && *s<128) { - stbtt_aligned_quad q; - float xx=x, yy=y; - stbtt_GetBakedQuad(cdata,tw,th,*s-32,&xx,&yy,&q,1); - float sx=q.x1-q.x0, sy=q.y1-q.y0; - gl_draw_quad(x+(q.x0-x)*scale, y+(q.y0-y)*scale, sx*scale, sy*scale, tex, c, q.s0, q.t0, q.s1, q.t1); - x+=(xx-x)*scale; - } else if (*s=='\n') { x=0; y+=baseline*1.3f; } - else { x+=8*scale; } - s++; - } - } - - float width(const char* s, float scale=1) const { - float x=0, y=0; - while (*s) { - if (*s>=32 && *s<128) { float xx=x,yy=y; stbtt_aligned_quad q; stbtt_GetBakedQuad(cdata,tw,th,*s-32,&xx,&yy,&q,1); x+=(xx-x)*scale; } - else if (*s!='\n') x+=8*scale; - s++; - } - return x; - } }; -// ---- PNG texture loader ----------------------------------------------------- -static GLuint load_png(const char* path, int* ow=nullptr, int* oh=nullptr) { - int w=0,h=0,n=0; unsigned char* px=stbi_load(path,&w,&h,&n,4); - if (!px) return 0; - GLuint t; glGenTextures(1,&t); glBindTexture(GL_TEXTURE_2D,t); - glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,px); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); - stbi_image_free(px); - if (ow) *ow=w; if (oh) *oh=h; - return t; -} - -// ---- UI state + interaction ------------------------------------------------- -struct UI { - int win_w=1280, win_h=960; - float mx=0, my=0; - bool mouse_down=false, mouse_pressed=false, mouse_released=false; - bool keys[SDL_NUM_SCANCODES]{}; - int hot_id=0, active_id=0, next_id=1, last_hot=0; - Font font; - // preloaded images - struct Img{GLuint t=0;int w=0,h=0;}; - Img logo, disc, pad_digital, pad_analog, memcard_img; - Img check_on, check_off, caret; - Img verdict_ok, verdict_warn, verdict_bad, verdict_none; - // running text-input buffer - char text_buf[256]={}; - - float scale=1, scroll_y=0; - float sz(float v) const { return v * scale; } - float ts(float v) const { return v * (20.f/48.f) * scale; } // font-bake-adjusted text scale - - void rect(float x,float y,float w,float h,const Color& c){gl_rect(x,y,w,h,c);} - void begin_frame(int w, int h) { - win_w=w; win_h=h; - scale = std::min((float)w/800.f, (float)h/600.f); - if (scale < 0.4f) scale = 0.4f; - if (scale > 3.0f) scale = 3.0f; - last_hot=hot_id; hot_id=0; next_id=1; - mouse_pressed=false; mouse_released=false; - gl_ortho(0,(float)w,(float)h,0); // y-down - gl_ensure_init(); - } - - int alloc_id() { return next_id++; } - - bool hot(int id, float x, float y, float w, float h) { - if (mx>=x && mx<=x+w && my>=y && my<=y+h) { hot_id=id; return true; } - return false; - } - - bool click(int id) { return hot_id==id && mouse_released && active_id==id; } - - bool button(int id, float x, float y, float w, float h) { - bool over=hot(id,x,y,w,h); - bool was_active=(active_id==id); - if (mouse_pressed && over) active_id=id; - if (mouse_released && was_active) { active_id=0; return over; } - if (mouse_released && active_id==id) active_id=0; - Color bg=THEME_BTN_BG; - if (was_active) bg=bg*1.3f; - else if (over) bg=THEME_BTN_HOVER; - gl_rect(x,y,w,h,bg); - return false; - } - - bool toggle(int id, float x, float y, float w, float h, bool on) { - bool over=hot(id,x,y,w,h); - if (mouse_pressed && over) active_id=id; - bool clk= mouse_released && active_id==id && over; - if (mouse_released && active_id==id) active_id=0; - Color bg=on ? THEME_ACCENT : THEME_BORDER; - if (over && !active_id) bg= bg==THEME_ACCENT ? THEME_ACCENT_HL : THEME_BORDER_HL; - if (active_id==id) bg=on ? THEME_ACCENT_HL : THEME_BORDER_HL; - gl_rect(x,y,w,h,bg); - float knob_x=on ? x+w-h : x; - gl_rect(knob_x+2,y+2,h-4,h-4,{0.902f,0.914f,0.937f,1}); - return clk; - } - - int segmented(int base, float x, float y, float w, float h, int cnt, int sel) { - int out=sel; float sw=w/cnt; - for (int i=0;ibool{ - auto p=assets_dir/rel; std::error_code ec; - if (!fs::exists(p,ec)) return false; - FILE*f=std::fopen(p.generic_string().c_str(),"rb"); if(!f) return false; - std::fseek(f,0,SEEK_END); long sz=std::ftell(f); std::fseek(f,0,SEEK_SET); - auto* buf=(unsigned char*)std::malloc(sz); - if (!buf) { std::fclose(f); return false; } - std::fread(buf,1,sz,f); std::fclose(f); - bool ok=ui.font.build(buf,px); - std::free(buf); - return ok; - }; - if (!load_font("fonts/LatoLatin-Regular.ttf",48)) { - if (!load_font("LatoLatin-Regular.ttf",48)) { - std::fprintf(stderr,"launcher: no font found\n"); - return false; - } - } - // images - auto load_img=[&](UI::Img& img, const char* rel) { - auto p=(assets_dir/rel).generic_string(); - img.t=load_png(p.c_str(),&img.w,&img.h); - }; - load_img(ui.logo, "img/logo.png"); - load_img(ui.disc, "img/disc.png"); - load_img(ui.pad_digital, "img/pad_digital.png"); - load_img(ui.pad_analog, "img/pad_analog.png"); - load_img(ui.memcard_img, "img/memcard.png"); - load_img(ui.check_on, "img/check_on.png"); - load_img(ui.check_off, "img/check_off.png"); - load_img(ui.caret, "img/caret.png"); - load_img(ui.verdict_ok, "img/verdict_ok.png"); - load_img(ui.verdict_warn, "img/verdict_warn.png"); - load_img(ui.verdict_bad, "img/verdict_bad.png"); - load_img(ui.verdict_none, "img/verdict_none.png"); - return true; -} - -// ---- business logic (ported verbatim from the original) --------------------- +// Mirror of the user-tunable settings, in the value shapes the RML binds to. struct LauncherModel { - int renderer=0, supersampling=1; - bool antialiasing=true, auto_skip_fmv=false, turbo_loads=true, fullscreen=false; - bool spu_hq=false, widescreen=false, ws_eligible=true, skip_launcher=false, show_skip_modal=false; - int texture_filter=0, crt=0, aspect_index=0, window_width=1280; - int p1_dev_index=1, p2_dev_index=0, p1_mode=0, p2_mode=0, deadzone_pct=37; - bool allow_hybrid=true, mode_selectable=true, lock_device=false, ws_offered=true, lang_menu=false; - int lang_index=0, cfg_player=0; - bool mc1_enabled=true, mc2_enabled=true, launch_requested=false, quit_requested=false; - std::string bios_path, disc_path, view="dashboard"; - std::string p1_dev_label="Keyboard", p2_dev_label="None", p1_status, p2_status, p1_dot, p2_dot; - std::string p1_options, p2_options, dd_open, lang_label; - std::string mc1_path, mc2_path, mc1_name, mc2_name, mc1_size, mc2_size, mc1_used, mc2_used, mc1_foot, mc2_foot; - std::string mc1_grid, mc2_grid; - std::string disc_file, disc_region, disc_serial, verdict_title, verdict_detail, verdict_state="none"; - bool v_header=false, v_crc=false, v_verified=false; + int renderer = 0; // 0=software, 1=opengl + int supersampling = 1; // 1..4 + bool antialiasing = true; + int texture_filter = 0; // 0=nearest, 1=bilinear + int crt = 0; // 0=raw,1=crt,2=composite,3=trinitron + bool auto_skip_fmv = false; // skip FMVs via the game's own skip + bool turbo_loads = true; // fast-forward the machine through load screens (audio plays through); default on + // (the old "Skip PSX BIOS" toggle is gone: the HLE boot shell-skip ships + // on by default via [runtime] bios_hle in the player game.toml) + bool spu_hq = false; + int aspect_index = 0; // index into kAspects (0 = 4:3 native) + int window_width = 1280; // window size (height = width*den/num per aspect) + bool widescreen = false; // EXPERIMENTAL 16:9 native-wide (aspect_index==1) + bool ws_eligible = true; // toggle shown only when renderer==software (native-wide is SW-only) + bool fullscreen = false; // launch the game window in desktop fullscreen + // Skip-launcher: boot straight into the game on subsequent launches. Turning + // it ON shows a confirmation modal (show_skip_modal) so the user is told how + // to get the launcher back (run with --launcher). Mirrors SMW's feature. + bool skip_launcher = false; + bool show_skip_modal = false; - // display labels (computed from the values above) - float uiscale = 1.0f; - std::string renderer_label, crt_label, texfilter_label, aspect_label, winsize_label, uiscale_label; + Rml::String bios_path; + Rml::String disc_path; - // keybind rebinding - int scan_kind=0, scan_index=0; - std::string scan_chip_id; - bool rebuild_pending=false; + // Display labels (kept in sync with the enum/int values above). + Rml::String renderer_label; + Rml::String crt_label; + Rml::String texfilter_label; + Rml::String aspect_label; + Rml::String winsize_label; + + // Disc verification (recomputed whenever disc_path changes). + Rml::String disc_file; // file name only, e.g. "tomba.cue" + Rml::String disc_region; // "NTSC-U (USA)" | "PAL" | "NTSC-J" | "—" + Rml::String disc_serial; // "SCUS-94236" | "—" + bool v_header = false; // ISO9660 header present + bool v_crc = false; // CRC/hash (or serial identity) check passed + bool v_verified = false; // overall verdict good + Rml::String verdict_title; // big line, e.g. "Tomba! disc verified" + Rml::String verdict_detail; // sub line + Rml::String verdict_state; // "ok" | "warn" | "bad" | "none" — drives colour + + // View toggle: "dashboard" (default) | "settings" | "controls". + Rml::String view = "dashboard"; + + // Controls page: which player's keyboard binds are being edited (0=P1,1=P2). + int cfg_player = 0; + Rml::String cfg_player_label = "1"; + + // Player cards — real device routing. Each port picks a device (None / + // Keyboard / a plugged-in SDL controller) and a pad type (DualShock=analog). + int p1_dev_index = 1; // index into the shared device option list + int p2_dev_index = 0; + // Pad input mode (PSXRecompV4::PadMode): 0=hybrid (default), 1=analog, + // 2=digital. Bound to the segmented 3-way selector in each player card. + int p1_mode = 0; + int p2_mode = 0; + bool allow_hybrid = true; // game.allow_hybrid: when false the Hybrid segment is hidden + bool mode_selectable = true; // game.lock_mode == false: when false the whole pad-mode selector is hidden + bool device_locked = false; // game.lock_device: when true the Player 1/2 cards are hidden entirely (fixed, auto-bound pad type) + int deadzone_pct = 37; // analog-stick deadzone 0-100% (raw = pct*32767/100) + Rml::String p1_dev_label = "Keyboard"; + Rml::String p2_dev_label = "None"; + Rml::String p1_status, p2_status; // resolved status line + Rml::String p1_dot, p2_dot; // "" (on) | "off" + Rml::String p1_options, p2_options; // data-rml option-list markup + Rml::String dd_open; // "" | "p1" | "p2" (which device list is open) + + // Localization (only shown when the game declares languages). Settings-view + // cycle button, like Renderer / Screen model. + bool lang_menu = false; // game.languages non-empty => show it + int lang_index = 0; // index into the game's language list + Rml::String lang_label; // current option label ("English") + + // Memory cards — real introspection of the on-disk .mcd images. Each slot + // has a resolved file path, an enable toggle, and parsed directory stats. + bool mc1_enabled = true; + bool mc2_enabled = true; + Rml::String mc1_path, mc2_path; // resolved absolute .mcd path + Rml::String mc1_name, mc2_name; // file name only + Rml::String mc1_size, mc2_size; // "128 KB (15 blocks)" | "—" + Rml::String mc1_used, mc2_used; // "7 / 15" | "—" + Rml::String mc1_foot, mc2_foot; // "Last modified — …" | status line + // 15-cell block grids, built as RML markup and injected via data-rml. (A + // data-for over a bound array is the natural fit, but the structural + // data-for view does not capture inner-xml in this build; data-rml is the + // robust path and the markup is fully launcher-controlled.) + Rml::String mc1_grid, mc2_grid; + + bool launch_requested = false; + bool quit_requested = false; }; -static const char* renderer_name(int v) { return v?"OpenGL":"Software"; } -static const char* texfilter_name(int v) { return v?"Bilinear":"Nearest"; } -static const char* crt_name(int v) { - switch(v){case 1:return "CRT";case 2:return "Composite";case 3:return "Trinitron";default:return "Raw (off)";} -} -static const int kAspects[][2]={{4,3},{16,9},{21,9}}; -static const int kNumAspects=3; -static const char* aspect_name(int i) { - switch(i){case 1:return "16:9 (Widescreen)";case 2:return "21:9 (Ultrawide)";default:return "4:3 (Native)";} -} -static int aspect_index_for(int num,int den){for(int i=0;ikWinWidths[i]?w-kWinWidths[i]:kWinWidths[i]-w;if(d enumerate_devices(){ - std::vector opts; opts.push_back({0,"","None"}); opts.push_back({1,"","Keyboard"}); - int n=SDL_NumJoysticks(); for(int i=0;i& opts, const std::string& dev){ - if(dev.empty()||dev=="none")return 0; if(dev=="keyboard")return 1; - for(size_t i=0;i& opts){ - int& idx=player==0?m.p1_dev_index:m.p2_dev_index; - std::string& label=player==0?m.p1_dev_label:m.p2_dev_label; - std::string& status=player==0?m.p1_status:m.p2_status; - std::string& dot=player==0?m.p1_dot:m.p2_dot; - int mode=player==0?m.p1_mode:m.p2_mode; - if(idx<0||idx>=(int)opts.size())idx=0; - label=opts[idx].label; - const char* type=mode==1?"DualShock (analog)":mode==2?"digital pad":"hybrid (auto analog/d-pad)"; - if(opts[idx].kind==0){status="No device \xE2\x80\x94 port empty"; dot="off";} - else if(opts[idx].kind==1){status=std::string("Keyboard \xE2\x80\x94 ")+type; dot="";} - else{status=opts[idx].label+" \xE2\x80\x94 "+type; dot="";} -} -static void refresh_language(LauncherModel& m, const std::vector& langs){ - if(langs.empty())return; if(m.lang_index<0||m.lang_index>=(int)langs.size())m.lang_index=0; m.lang_label=langs[m.lang_index].label; -} -static int lang_index_for(const std::vector& langs, const std::string& code){ - for(size_t i=0;i/card.mcd. +std::string memcard_slot_path(const PSXRecompV4::UserSettings& io, int slot /*0|1*/) { + const bool has = slot == 0 ? io.has_memcard1_path : io.has_memcard2_path; + const std::filesystem::path& p = slot == 0 ? io.memcard1_path : io.memcard2_path; + if (has && !p.empty()) return p.generic_string(); + fs::path dir = io.has_memcard_dir ? io.memcard_dir : fs::path(); + if (dir.empty()) return std::string(); + return (dir / (std::string("card") + (slot == 0 ? "1" : "2") + ".mcd")).generic_string(); } -// disc verification -static void refresh_disc_status(LauncherModel& m, const std::string& game_name, - const std::string& expected_serial, uint32_t expected_crc, bool has_expected_crc){ - m.v_header=m.v_crc=m.v_verified=false; m.disc_region=m.disc_serial="\xE2\x80\x94"; m.disc_file="\xE2\x80\x94"; - if(m.disc_path.empty()){m.verdict_title="No disc selected"; m.verdict_detail="Choose a disc image to verify it against this build."; m.verdict_state="none"; return;} - m.disc_file=fs::path(m.disc_path).filename().generic_string(); - auto id=PSXRecompV4::identify_disc(fs::path(m.disc_path),expected_serial,expected_crc,has_expected_crc,has_expected_crc); - if(!id.opened){m.verdict_title="Disc not found"; m.verdict_detail="Could not open the image or its CUE-referenced BIN."; m.verdict_state="bad"; return;} - if(!id.has_header){m.verdict_title="Not a PlayStation disc"; m.verdict_detail="No ISO9660 header at the expected sectors."; m.verdict_state="bad"; return;} - m.v_header=true; - if(!id.detected_serial.empty())m.disc_serial=id.detected_serial; else if(!expected_serial.empty())m.disc_serial=expected_serial; - if(!id.region.empty())m.disc_region=region_long(id.region); - bool serial_ok=!id.expected_serial_given||id.serial_matches; - if(id.expected_crc_given&&id.crc_computed) m.v_crc=id.crc_matches; else m.v_crc=serial_ok&&id.expected_serial_given; - m.v_verified=m.v_header&&serial_ok&&(!id.expected_crc_given||id.crc_matches); - const std::string nm=game_name.empty()?"Disc":game_name; - if(m.v_verified){m.verdict_title=nm+" disc verified"; m.verdict_detail="Correct disc image loaded. Ready to launch."; m.verdict_state="ok";} - else if(!serial_ok){m.verdict_title="Wrong disc?"; m.verdict_detail="Serial does not match this build (expected "+expected_serial+")."; m.verdict_state="bad";} - else if(id.expected_crc_given&&id.crc_computed&&!id.crc_matches){m.verdict_title="Disc image differs"; m.verdict_detail="Right game, but the image hash does not match the expected dump."; m.verdict_state="warn";} - else{m.verdict_title="PlayStation disc"; m.verdict_detail="Recognised PlayStation disc. No reference hash configured."; m.verdict_state="ok"; m.v_verified=true;} +// Parse the slot's .mcd file and fill the model's display fields for it. +void refresh_memcard(LauncherModel& m, int slot /*0|1*/) { + Rml::String& path = slot == 0 ? m.mc1_path : m.mc2_path; + Rml::String& name = slot == 0 ? m.mc1_name : m.mc2_name; + Rml::String& size = slot == 0 ? m.mc1_size : m.mc2_size; + Rml::String& used = slot == 0 ? m.mc1_used : m.mc2_used; + Rml::String& foot = slot == 0 ? m.mc1_foot : m.mc2_foot; + Rml::String& grid = slot == 0 ? m.mc1_grid : m.mc2_grid; + + auto build_grid = [](const uint8_t used[15]) { + Rml::String html; + for (int i = 0; i < 15; i++) + html += used[i] ? "" : ""; + return html; + }; + const uint8_t empty15[15] = {0}; + grid = build_grid(empty15); + name = path.empty() ? Rml::String("(no card)") + : fs::path(std::string(path)).filename().generic_string(); + + if (path.empty()) { + size = used = "—"; + foot = "No card configured."; + return; + } + + MemcardSummary s; + memcard_summary_path(std::string(path).c_str(), &s); + grid = build_grid(s.block_used); + + if (!s.exists) { + size = "128 KB (15 blocks)"; + used = "0 / 15"; + foot = "New blank card — created on launch."; + return; + } + if (!s.valid) { + size = used = "—"; + foot = "Not a valid memory-card image."; + return; + } + size = "128 KB (15 blocks)"; + used = std::to_string(s.used_blocks) + " / 15"; + const std::string when = fmt_mtime(s.mtime); + foot = when.empty() ? Rml::String("On-disk memory card.") + : Rml::String("Last modified — " + when); +} + +// ---- input-device enumeration (None / Keyboard / plugged-in controllers) ---- +struct DeviceOption { + int kind; // 0=none, 1=keyboard, 2=controller + std::string guid; // SDL joystick GUID string when kind==controller + std::string label; // display name +}; + +std::vector enumerate_devices() { + std::vector opts; + opts.push_back({0, "", "None"}); + opts.push_back({1, "", "Keyboard"}); + const int n = SDL_NumJoysticks(); + for (int i = 0; i < n; i++) { + if (!SDL_IsGameController(i)) continue; + SDL_JoystickGUID g = SDL_JoystickGetDeviceGUID(i); + char buf[40] = {0}; + SDL_JoystickGetGUIDString(g, buf, sizeof(buf)); + const char* nm = SDL_GameControllerNameForIndex(i); + opts.push_back({2, std::string(buf), nm ? std::string(nm) : std::string("Controller")}); + } + return opts; +} + +// The settings device string ("none"/"keyboard"/) for an option. +std::string device_string(const DeviceOption& o) { + if (o.kind == 0) return "none"; + if (o.kind == 1) return "keyboard"; + return o.guid; +} + +// Minimal RML/attribute text escape for injected option labels. +std::string rml_escape(const std::string& s) { + std::string o; + for (char c : s) { + switch (c) { + case '&': o += "&"; break; + case '<': o += "<"; break; + case '>': o += ">"; break; + case '"': o += """; break; + case '\'':o += "'"; break; + default: o += c; break; + } + } + return o; +} + +// Resolve a saved device string to an index in opts. A saved controller GUID +// that is not currently plugged in is appended as an "(offline)" option so the +// user's selection survives across unplug/replug. +int find_or_add_device_index(std::vector& opts, const std::string& dev) { + if (dev.empty() || dev == "none") return 0; + if (dev == "keyboard") return 1; + for (size_t i = 0; i < opts.size(); i++) + if (opts[i].kind == 2 && opts[i].guid == dev) return (int)i; + opts.push_back({2, dev, "Saved controller (offline)"}); + return (int)opts.size() - 1; +} + +// Build the data-rml option-list markup for a player's dropdown. Each row is a +// clickable element whose data-event-click selects that option (and closes). +std::string build_options_rml(int player, const std::vector& opts) { + std::string s; + for (size_t i = 0; i < opts.size(); i++) { + s += "

"; + s += rml_escape(opts[i].label); + s += "

"; + } + return s; +} + +// Recompute a player's derived display fields from its selected option index. +void refresh_player(LauncherModel& m, int player, const std::vector& opts) { + int& idx = player == 0 ? m.p1_dev_index : m.p2_dev_index; + Rml::String& label = player == 0 ? m.p1_dev_label : m.p2_dev_label; + Rml::String& status = player == 0 ? m.p1_status : m.p2_status; + Rml::String& dot = player == 0 ? m.p1_dot : m.p2_dot; + Rml::String& options= player == 0 ? m.p1_options : m.p2_options; + const int mode = player == 0 ? m.p1_mode : m.p2_mode; + + if (idx < 0 || idx >= (int)opts.size()) idx = 0; + const DeviceOption& o = opts[idx]; + label = o.label; + options = build_options_rml(player, opts); + + const char* type = mode == 1 ? "DualShock (analog)" + : mode == 2 ? "digital pad" + : "hybrid (auto analog/d-pad)"; + if (o.kind == 0) { status = "No device — port empty"; dot = "off"; } + else if (o.kind == 1) { status = Rml::String("Keyboard \xE2\x80\x94 ") + type; dot = ""; } + else { status = o.label + Rml::String(" \xE2\x80\x94 ") + type; dot = ""; } +} + +// Recompute the language button's label from lang_index (Settings cycle toggle). +void refresh_language(LauncherModel& m, + const std::vector& langs) { + if (langs.empty()) return; + if (m.lang_index < 0 || m.lang_index >= (int)langs.size()) m.lang_index = 0; + m.lang_label = langs[m.lang_index].label; +} + +// Resolve a saved language code to an index in the game's language list. +int lang_index_for(const std::vector& langs, + const std::string& code) { + for (size_t i = 0; i < langs.size(); i++) + if (langs[i].code == code) return (int)i; + return 0; // unknown/first — the game's declared default sits at [0] by convention +} + +const char* renderer_name(int v) { return v == 1 ? "OpenGL" : "Software"; } +const char* texfilter_name(int v) { return v == 1 ? "Bilinear" : "Nearest"; } +const char* crt_name(int v) { + switch (v) { + case 1: return "CRT"; + case 2: return "Composite"; + case 3: return "Trinitron"; + default: return "Raw (off)"; + } +} + +// Offered display aspects. 4:3 is the native presentation every game ships +// with; wider aspects enable the runtime widescreen hack (GTE X-squash + +// stretched present — see [video] aspect_ratio in config_loader.h). +const int kAspects[][2] = { {4, 3}, {16, 9}, {21, 9} }; +const int kNumAspects = (int)(sizeof(kAspects) / sizeof(kAspects[0])); +const char* aspect_name(int i) { + switch (i) { + case 1: return "16:9 (Widescreen)"; + case 2: return "21:9 (Ultrawide)"; + default: return "4:3 (Native)"; + } +} +int aspect_index_for(int num, int den) { + for (int i = 0; i < kNumAspects; i++) + if (kAspects[i][0] == num && kAspects[i][1] == den) return i; + return 0; +} + +// Offered window widths (height follows the chosen aspect). The toggle cycles +// through these. +const int kWinWidths[] = { 960, 1280, 1600, 1920 }; +const int kNumWinWidths = (int)(sizeof(kWinWidths) / sizeof(kWinWidths[0])); + +// Snap an arbitrary width to the nearest offered option index. +int winsize_index(int width) { + int best = 1, bestd = 1 << 30; // default to 1280 + for (int i = 0; i < kNumWinWidths; i++) { + int d = width > kWinWidths[i] ? width - kWinWidths[i] : kWinWidths[i] - width; + if (d < bestd) { bestd = d; best = i; } + } + return best; +} + +std::string winsize_label_for(int width, int aspect_index) { + const int num = kAspects[aspect_index][0], den = kAspects[aspect_index][1]; + return std::to_string(width) + " \xC3\x97 " + std::to_string(width * den / num); // "1280 × 960" +} + +void refresh_labels(LauncherModel& m) { + m.renderer_label = renderer_name(m.renderer); + m.crt_label = crt_name(m.crt); + m.texfilter_label = texfilter_name(m.texture_filter); + m.aspect_label = aspect_name(m.aspect_index); + m.winsize_label = winsize_label_for(m.window_width, m.aspect_index); + m.widescreen = (m.aspect_index == 1); // 16:9 == experimental native-wide + /* ws_eligible is set once at model init from GameInfo.ws_offered ([widescreen] + * offer): renderer no longer matters (native-wide works on SW + GL), so + * refresh must not overwrite the per-game gate. */ +} + +std::string region_long(const std::string& r) { + if (r == "NTSC-U") return "NTSC-U (USA)"; + if (r == "NTSC-J") return "NTSC-J (Japan)"; + if (r == "PAL") return "PAL (Europe)"; + return r; +} + +// Re-run disc verification against m.disc_path and update the panel fields. +// `expected_serial`/`expected_crc` come from game.toml (via GameInfo); +// `game_name` is used for the verdict headline. +void refresh_disc_status(LauncherModel& m, const std::string& game_name, + const std::string& expected_serial, + uint32_t expected_crc, bool has_expected_crc) { + m.v_header = m.v_crc = m.v_verified = false; + m.disc_region = m.disc_serial = "—"; + m.disc_file = "—"; + + if (m.disc_path.empty()) { + m.verdict_title = "No disc selected"; + m.verdict_detail = "Choose a disc image to verify it against this build."; + m.verdict_state = "none"; + return; + } + m.disc_file = fs::path(std::string(m.disc_path)).filename().generic_string(); + + // Only spend time hashing when there is an expected CRC to compare against. + const PSXRecompV4::DiscIdentity id = PSXRecompV4::identify_disc( + fs::path(std::string(m.disc_path)), expected_serial, + expected_crc, has_expected_crc, /*compute_crc=*/has_expected_crc); + + if (!id.opened) { + m.verdict_title = "Disc not found"; + m.verdict_detail = "Could not open the image or its CUE-referenced BIN."; + m.verdict_state = "bad"; + return; + } + if (!id.has_header) { + m.verdict_title = "Not a PlayStation disc"; + m.verdict_detail = "No ISO9660 header at the expected sectors."; + m.verdict_state = "bad"; + return; + } + m.v_header = true; + if (!id.detected_serial.empty()) m.disc_serial = id.detected_serial; + else if (!expected_serial.empty()) m.disc_serial = expected_serial; + if (!id.region.empty()) m.disc_region = region_long(id.region); + + const bool serial_ok = !id.expected_serial_given || id.serial_matches; + + // Middle check: exact CRC match if we have one to compare, otherwise the + // serial-based identity match stands in for the hash. + if (id.expected_crc_given && id.crc_computed) + m.v_crc = id.crc_matches; + else + m.v_crc = serial_ok && id.expected_serial_given; + + m.v_verified = m.v_header && serial_ok && + (!id.expected_crc_given || id.crc_matches); + + const std::string nm = game_name.empty() ? std::string("Disc") : game_name; + if (m.v_verified) { + m.verdict_title = nm + " disc verified"; + m.verdict_detail = "Correct disc image loaded. Ready to launch."; + m.verdict_state = "ok"; + } else if (!serial_ok) { + m.verdict_title = "Wrong disc?"; + m.verdict_detail = "Serial does not match this build (expected " + expected_serial + ")."; + m.verdict_state = "bad"; + } else if (id.expected_crc_given && id.crc_computed && !id.crc_matches) { + m.verdict_title = "Disc image differs"; + m.verdict_detail = "Right game, but the image hash does not match the expected dump."; + m.verdict_state = "warn"; + } else { + // Header ok, nothing authoritative to compare against. + m.verdict_title = "PlayStation disc"; + m.verdict_detail = "Recognised PlayStation disc. No reference hash configured."; + m.verdict_state = "ok"; + m.v_verified = true; + } } -// ---- file picker (unchanged) ------------------------------------------------ #if defined(_WIN32) -static std::string win_pick_file(SDL_Window*, const char* title, const char* filter){ - char buf[MAX_PATH]={0}; OPENFILENAMEA ofn={0}; ofn.lStructSize=sizeof(ofn); ofn.lpstrFilter=filter; ofn.lpstrFile=buf; ofn.nMaxFile=sizeof(buf); ofn.lpstrTitle=title; ofn.Flags=OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_NOCHANGEDIR; - return GetOpenFileNameA(&ofn)?std::string(buf):std::string(); +// Native open-file dialog. Returns "" if cancelled. +std::string win_pick_file(SDL_Window* parent, const char* title, const char* filter) { + char buf[MAX_PATH] = {0}; + OPENFILENAMEA ofn = {0}; + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = filter; // e.g. "BIOS (*.bin)\0*.bin\0All files\0*.*\0\0" + ofn.lpstrFile = buf; + ofn.nMaxFile = sizeof(buf); + ofn.lpstrTitle = title; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR; + (void)parent; + if (GetOpenFileNameA(&ofn)) return std::string(buf); + return std::string(); } -static std::string win_pick_save_file(SDL_Window*, const char* title, const char* filter, const char* def_ext, const std::string& init){ - char buf[MAX_PATH]={0}; if(!init.empty()){std::snprintf(buf,sizeof(buf),"%s",init.c_str());for(char*p=buf;*p;++p)if(*p=='/')*p='\\';} - OPENFILENAMEA ofn={0}; ofn.lStructSize=sizeof(ofn); ofn.lpstrFilter=filter; ofn.lpstrFile=buf; ofn.nMaxFile=sizeof(buf); ofn.lpstrTitle=title; ofn.lpstrDefExt=def_ext; ofn.Flags=OFN_OVERWRITEPROMPT|OFN_PATHMUSTEXIST|OFN_NOCHANGEDIR; - return GetSaveFileNameA(&ofn)?std::string(buf):std::string(); + +// Native save-file dialog (overwrite-prompts). Returns "" if cancelled. +std::string win_pick_save_file(SDL_Window* parent, const char* title, + const char* filter, const char* default_ext, + const std::string& initial) { + char buf[MAX_PATH] = {0}; + if (!initial.empty()) { + std::snprintf(buf, sizeof(buf), "%s", initial.c_str()); + for (char* p = buf; *p; ++p) if (*p == '/') *p = '\\'; // native sep + } + OPENFILENAMEA ofn = {0}; + ofn.lStructSize = sizeof(ofn); + ofn.lpstrFilter = filter; + ofn.lpstrFile = buf; + ofn.nMaxFile = sizeof(buf); + ofn.lpstrTitle = title; + ofn.lpstrDefExt = default_ext; // appended if the user types no extension + ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR; + (void)parent; + if (GetSaveFileNameA(&ofn)) return std::string(buf); + return std::string(); } #else +// POSIX native dialogs via popen-based choosers (zenity / kdialog / qarma / +// osascript), each gated on `command -v` so an absent tool falls through. +// Returns "" on cancel or when no chooser is installed. The Win32 double-null +// `filter` string isn't portable, so the POSIX path uses a generic chooser +// (the title is honored); BIOS / disc / memory-card browsing works on Linux. #include -static std::string sh_squote(const std::string& s){std::string q="'";for(char c:s){if(c=='\'')q+="'\\''";else q+=c;}return q+"'";} -static std::string run_chooser(const std::string& cmd){ - std::string o; FILE* p=popen(cmd.c_str(),"r"); if(!p)return o; char buf[2048]; if(fgets(buf,sizeof(buf),p))o=buf; int rc=pclose(p); - while(!o.empty()&&(o.back()=='\n'||o.back()=='\r'))o.pop_back(); if(rc!=0)o.clear(); return o; +namespace { +std::string sh_squote(const std::string& s) { + std::string q = "'"; + for (char c : s) { if (c == '\'') q += "'\\''"; else q += c; } + return q + "'"; } -static std::string win_pick_file(SDL_Window*, const char* title, const char*){ - std::string t=sh_squote(title?title:"Select file"); std::string r; - if(!(r=run_chooser("command -v zenity >/dev/null 2>&1 && zenity --file-selection --title="+t+" 2>/dev/null")).empty())return r; - if(!(r=run_chooser("command -v kdialog >/dev/null 2>&1 && kdialog --getopenfilename \"${HOME:-/}\" 2>/dev/null")).empty())return r; - if(!(r=run_chooser("command -v qarma >/dev/null 2>&1 && qarma --file-selection --title="+t+" 2>/dev/null")).empty())return r; - return run_chooser("command -v osascript >/dev/null 2>&1 && osascript -e 'POSIX path of (choose file)' 2>/dev/null"); +std::string run_chooser(const std::string& cmd) { + std::string out; + FILE* p = popen(cmd.c_str(), "r"); + if (!p) return out; + char buf[2048]; + if (fgets(buf, sizeof(buf), p)) out = buf; + int rc = pclose(p); + while (!out.empty() && (out.back() == '\n' || out.back() == '\r')) out.pop_back(); + if (rc != 0) out.clear(); + return out; } -static std::string win_pick_save_file(SDL_Window*, const char* title, const char*, const char*, const std::string& init){ - std::string t=sh_squote(title?title:"Save file"); std::string r; - std::string z="command -v zenity >/dev/null 2>&1 && zenity --file-selection --save --confirm-overwrite --title="+t; - if(!init.empty())z+=" --filename="+sh_squote(init); - if(!(r=run_chooser(z+" 2>/dev/null")).empty())return r; - std::string k="command -v kdialog >/dev/null 2>&1 && kdialog --getsavefilename "; - k+=init.empty()?std::string("\"${HOME:-/}\""):sh_squote(init); - return run_chooser(k+" 2>/dev/null"); +} // namespace + +std::string win_pick_file(SDL_Window*, const char* title, const char*) { + std::string t = sh_squote(title ? title : "Select file"); + std::string r; + if (!(r = run_chooser("command -v zenity >/dev/null 2>&1 && " + "zenity --file-selection --title=" + t + " 2>/dev/null")).empty()) return r; + if (!(r = run_chooser("command -v kdialog >/dev/null 2>&1 && " + "kdialog --getopenfilename \"${HOME:-/}\" 2>/dev/null")).empty()) return r; + if (!(r = run_chooser("command -v qarma >/dev/null 2>&1 && " + "qarma --file-selection --title=" + t + " 2>/dev/null")).empty()) return r; + return run_chooser("command -v osascript >/dev/null 2>&1 && " + "osascript -e 'POSIX path of (choose file)' 2>/dev/null"); +} + +std::string win_pick_save_file(SDL_Window*, const char* title, const char*, + const char*, const std::string& initial) { + std::string t = sh_squote(title ? title : "Save file"); + std::string r; + std::string z = "command -v zenity >/dev/null 2>&1 && " + "zenity --file-selection --save --confirm-overwrite --title=" + t; + if (!initial.empty()) z += " --filename=" + sh_squote(initial); + if (!(r = run_chooser(z + " 2>/dev/null")).empty()) return r; + std::string k = "command -v kdialog >/dev/null 2>&1 && kdialog --getsavefilename "; + k += initial.empty() ? std::string("\"${HOME:-/}\"") : sh_squote(initial); + return run_chooser(k + " 2>/dev/null"); } #endif -// ---- view renderers --------------------------------------------------------- - -static float render_dashboard(UI& ui, LauncherModel& m, - const std::vector& dev_opts, - SDL_Window* window, - const std::string& game_name, - const std::string& expected_serial, - uint32_t expected_crc, bool has_expected_crc, - const std::vector& langs) -{ - const float s = ui.scale; - const float W=(float)ui.win_w, H=(float)ui.win_h; - const float pad=ui.sz(22), gap=ui.sz(13); - float y=pad+ui.sz(46)+gap; // skip top bar (drawn by main loop) - float mgn=ui.sz(18); - - // account for scroll - y += ui.scroll_y; - - // ---- disc verification panel ---- - float disc_h=ui.sz(220), dp_y=y, dp_x=pad, dp_w=W-2*pad; - ui.rect(dp_x,dp_y,dp_w,disc_h,THEME_PANEL); - ui.rect(dp_x,dp_y,dp_w,1,THEME_BORDER); - float art_s=ui.sz(180); - if (ui.disc.t) gl_draw_quad(dp_x+mgn,dp_y+(disc_h-art_s)/2,art_s,art_s,ui.disc.t,{1,1,1,1}); - float di_x=dp_x+mgn+art_s+ui.sz(20); - ui.font.text(di_x,dp_y+ui.sz(16),"DISC VERIFICATION",THEME_ACCENT,ui.ts(0.6f)); - float title_sz=ui.ts(1.0f); - ui.font.text(di_x,dp_y+ui.sz(36),game_name.c_str(),THEME_TEXT,title_sz); - ui.font.text(di_x,dp_y+ui.sz(36)+ui.font.baseline*title_sz+ui.sz(2),"PlayStation disc image",THEME_TEXT_DIM,ui.ts(0.7f)); - // metadata - float meta_y=dp_y+ui.sz(36)+ui.font.baseline*title_sz+ui.sz(2)+ui.sz(16); - auto meta=[&](const char* k, const std::string& v, float& cx){ - ui.font.text(cx,meta_y,k,THEME_TEXT_MUTED,ui.ts(0.55f)); - ui.font.text(cx,meta_y+ui.sz(12),v.c_str(),THEME_TEXT,ui.ts(0.7f)); - cx+=std::max(ui.font.width(k,ui.ts(0.55f)),ui.font.width(v.c_str(),ui.ts(0.7f)))+ui.sz(40); +// Load at least one font face so RmlUi can render text. Tries bundled fonts in +// assets_dir, then a couple of platform fallbacks. Returns true if any loaded. +bool load_fonts(const fs::path& assets_dir) { + const char* bundled[] = { + "fonts/LatoLatin-Regular.ttf", + "fonts/LatoLatin-Bold.ttf", + "LatoLatin-Regular.ttf", }; - float cx=di_x; meta("FILE",m.disc_file,cx); meta("REGION",m.disc_region,cx); meta("SERIAL",m.disc_serial,cx); - // verification checks - float chk_y=meta_y+ui.sz(12)+ui.sz(18); - ui.font.text(di_x,chk_y,"VERIFICATION RESULTS",THEME_ACCENT,ui.ts(0.55f)); - auto check=[&](const char* lbl, bool on, float& cx2){ - auto& img=on?ui.check_on:ui.check_off; - float chk_sz=ui.sz(16); - if (img.t) gl_draw_quad(cx2,chk_y+ui.sz(14),chk_sz,chk_sz,img.t,{1,1,1,1}); - ui.font.text(cx2+ui.sz(20),chk_y+ui.sz(14),lbl,THEME_TEXT_DIM,ui.ts(0.6f)); - cx2+=ui.sz(20)+ui.font.width(lbl,ui.ts(0.6f))+ui.sz(24); - }; - float chk_x=di_x; check("Header Match",m.v_header,chk_x); check("CRC / Hash Match",m.v_crc,chk_x); check("Disc Verified",m.v_verified,chk_x); - // verdict column - float vc_w=ui.sz(260); - float vc_x=dp_x+dp_w-vc_w-mgn; - ui.rect(vc_x,dp_y+ui.sz(12),1,disc_h-ui.sz(24),THEME_BORDER); - auto vimg=[&]()->UI::Img{ - if (m.verdict_state=="ok") return ui.verdict_ok; - if (m.verdict_state=="warn") return ui.verdict_warn; - if (m.verdict_state=="bad") return ui.verdict_bad; - return ui.verdict_none; - }(); - float vimg_sz=ui.sz(50); - if (vimg.t) gl_draw_quad(vc_x+ui.sz(20),dp_y+(disc_h-vimg_sz)/2,vimg_sz,vimg_sz,vimg.t,{1,1,1,1}); - ui.font.text(vc_x+vimg_sz+ui.sz(30),dp_y+disc_h/2-ui.sz(18),m.verdict_title.c_str(),THEME_TEXT,ui.ts(0.8f)); - ui.font.text(vc_x+vimg_sz+ui.sz(30),dp_y+disc_h/2+ui.sz(2),m.verdict_detail.c_str(),THEME_TEXT_MUTED,ui.ts(0.55f)); - // change ISO button - float iso_bw=ui.sz(100), iso_bh=ui.sz(30); - float iso_x=vc_x-iso_bw-mgn+vc_w; - if (ui.button(ui.alloc_id(),iso_x,dp_y+ui.sz(12),iso_bw,iso_bh)) { - std::string p=win_pick_file(window,"Select disc image","Disc image (*.cue;*.bin;*.iso)\0*.cue;*.bin;*.iso\0All files (*.*)\0*.*\0\0"); - if (!p.empty()){ m.disc_path=fs::path(p).generic_string(); refresh_disc_status(m,game_name,expected_serial,expected_crc,has_expected_crc); } - } float lbw=ui.font.width("Change ISO",ui.ts(0.6f)); - ui.font.text(iso_x+(iso_bw-lbw)/2,dp_y+ui.sz(12)+(iso_bh-ui.font.baseline*ui.ts(0.6f))/2,"Change ISO",THEME_TEXT,ui.ts(0.6f)); - y=dp_y+disc_h+gap; - - // ---- player cards (stacked vertically) ---- - float cw=W-2*pad, ch=ui.sz(190); - for (int pl=0;pl<2;pl++){ - float px=pad, py=y + pl*(ch+gap); - ui.rect(px,py,cw,ch,THEME_PANEL); ui.rect(px,py,cw,1,THEME_BORDER); - ui.font.text(px+mgn,py+ui.sz(12),pl==0?"PLAYER 1":"PLAYER 2",THEME_ACCENT,ui.ts(0.55f)); - int& mode=pl==0?m.p1_mode:m.p2_mode; - if (m.mode_selectable) { - const char* seg_opts[]={"Hybrid","Analog","D-Pad"}; - int n=m.allow_hybrid?3:2; - float seg_w=ui.sz(200), seg_h=ui.sz(24), seg_x=px+cw-mgn-seg_w, seg_y=py+ui.sz(10); - float sw=seg_w/n; - for (int i=0;i= ddx && ui.mx <= ddx+ddw && ui.my >= dpnl_y && ui.my <= dpnl_y+dpnl_h; - ui.rect(ddx,dpnl_y,ddw,dpnl_h,THEME_DROPDOWN_BG); - ui.rect(ddx,dpnl_y,ddw,1,THEME_BORDER); - float row_h_dd=ui.sz(28); - for (size_t i=0;i dev_opts = enumerate_devices(); - m.p1_mode = io.has_p1_mode ? io.p1_mode : 0; - m.p2_mode = io.has_p2_mode ? io.p2_mode : 0; + m.p1_mode = io.has_p1_mode ? io.p1_mode : PSXRecompV4::PAD_MODE_HYBRID; + m.p2_mode = io.has_p2_mode ? io.p2_mode : PSXRecompV4::PAD_MODE_HYBRID; + // When the game hides Hybrid, never leave a port selected on it (a stale + // settings.toml or the Hybrid default would otherwise highlight nothing). m.allow_hybrid = game.allow_hybrid; if (!m.allow_hybrid) { - if (m.p1_mode == 0) m.p1_mode = 1; - if (m.p2_mode == 0) m.p2_mode = 1; + if (m.p1_mode == PSXRecompV4::PAD_MODE_HYBRID) m.p1_mode = PSXRecompV4::PAD_MODE_ANALOG; + if (m.p2_mode == PSXRecompV4::PAD_MODE_HYBRID) m.p2_mode = PSXRecompV4::PAD_MODE_ANALOG; } + // lock_mode: a single-pad-type game (e.g. Tomba 2, digital-only). Hide the + // whole pad-mode selector and force both ports to the game's locked mode, + // overriding any stale settings.toml so a broken mode can't be selected. m.mode_selectable = !game.lock_mode; - if (game.lock_mode) { m.p1_mode = game.locked_mode; m.p2_mode = game.locked_mode; } - m.lock_device = game.lock_device; - m.ws_offered = game.ws_offered; + if (game.lock_mode) { + m.p1_mode = game.locked_mode; + m.p2_mode = game.locked_mode; + } + // lock_device: hide the Player controller cards entirely — the pad type is + // fixed and auto-bound (e.g. Ape Escape ships DualShock analog, no choice). + m.device_locked = game.lock_device; m.deadzone_pct = io.has_deadzone ? (io.deadzone * 100 / 32767) : 37; - m.uiscale = 1.0f; // TODO: persist uiscale in UserSettings - m.p1_dev_index = io.has_p1_device ? find_or_add_device_index(dev_opts, io.p1_device) : (dev_opts.size()>2?2:1); + if (io.has_p1_device) { + m.p1_dev_index = find_or_add_device_index(dev_opts, io.p1_device); + } else { + // Zero-config default: first plugged-in controller, else keyboard. + m.p1_dev_index = (dev_opts.size() > 2) ? 2 : 1; + } m.p2_dev_index = io.has_p2_device ? find_or_add_device_index(dev_opts, io.p2_device) : 0; refresh_player(m, 0, dev_opts); refresh_player(m, 1, dev_opts); + // ---- Seed the localization dropdown (only when the game declares one) ---- m.lang_menu = !game.languages.empty(); - if (m.lang_menu) { m.lang_index = lang_index_for(game.languages, io.language); refresh_language(m, game.languages); } + if (m.lang_menu) { + m.lang_index = lang_index_for(game.languages, io.language); + refresh_language(m, game.languages); + } - // GL setup happens in ui.begin_frame which calls gl_ensure_init + // ---- Data model: bind fields + action callbacks ---- + Rml::DataModelConstructor c = context->CreateDataModel("settings"); + if (!c) { + Rml::Shutdown(); + RmlGL3::Shutdown(); + return Result::Unavailable; + } + Rml::String title = game.name ? Rml::String(game.name) : Rml::String("PSX"); + c.BindFunc("game_name", [title](Rml::Variant& out) { out = title; }); + c.Bind("supersampling", &m.supersampling); + c.Bind("antialiasing", &m.antialiasing); + c.Bind("auto_skip_fmv", &m.auto_skip_fmv); + c.Bind("turbo_loads", &m.turbo_loads); + c.Bind("fullscreen", &m.fullscreen); + c.Bind("skip_launcher", &m.skip_launcher); + c.Bind("show_skip_modal",&m.show_skip_modal); + c.Bind("spu_hq", &m.spu_hq); + c.Bind("renderer_label", &m.renderer_label); + c.Bind("crt_label", &m.crt_label); + c.Bind("aspect_label", &m.aspect_label); + c.Bind("widescreen", &m.widescreen); + c.Bind("ws_eligible", &m.ws_eligible); + c.Bind("winsize_label", &m.winsize_label); + c.Bind("texfilter_label",&m.texfilter_label); + c.Bind("bios_path", &m.bios_path); + c.Bind("disc_path", &m.disc_path); + c.Bind("disc_file", &m.disc_file); + c.Bind("disc_region", &m.disc_region); + c.Bind("disc_serial", &m.disc_serial); + c.Bind("v_header", &m.v_header); + c.Bind("v_crc", &m.v_crc); + c.Bind("v_verified", &m.v_verified); + c.Bind("verdict_title", &m.verdict_title); + c.Bind("verdict_detail", &m.verdict_detail); + c.Bind("verdict_state", &m.verdict_state); + c.Bind("view", &m.view); + c.Bind("cfg_player", &m.cfg_player); + c.Bind("cfg_player_label", &m.cfg_player_label); + c.Bind("p1_mode", &m.p1_mode); + c.Bind("p2_mode", &m.p2_mode); + c.Bind("allow_hybrid", &m.allow_hybrid); + c.Bind("mode_selectable",&m.mode_selectable); + c.Bind("device_locked", &m.device_locked); + c.Bind("deadzone_pct", &m.deadzone_pct); + c.Bind("p1_dev_label", &m.p1_dev_label); + c.Bind("p2_dev_label", &m.p2_dev_label); + c.Bind("p1_status", &m.p1_status); + c.Bind("p2_status", &m.p2_status); + c.Bind("p1_dot", &m.p1_dot); + c.Bind("p2_dot", &m.p2_dot); + c.Bind("p1_options", &m.p1_options); + c.Bind("p2_options", &m.p2_options); + c.Bind("dd_open", &m.dd_open); + c.Bind("lang_menu", &m.lang_menu); + c.Bind("lang_label", &m.lang_label); + c.Bind("mc1_enabled", &m.mc1_enabled); + c.Bind("mc2_enabled", &m.mc2_enabled); + c.Bind("mc1_name", &m.mc1_name); + c.Bind("mc2_name", &m.mc2_name); + c.Bind("mc1_size", &m.mc1_size); + c.Bind("mc2_size", &m.mc2_size); + c.Bind("mc1_used", &m.mc1_used); + c.Bind("mc2_used", &m.mc2_used); + c.Bind("mc1_foot", &m.mc1_foot); + c.Bind("mc2_foot", &m.mc2_foot); + c.Bind("mc1_grid", &m.mc1_grid); + c.Bind("mc2_grid", &m.mc2_grid); - // ---- main loop ---- + Rml::DataModelHandle handle = c.GetModelHandle(); + + // ---- keyboard keybind rebinding (Controls page) -------------------------- + // The chip list is built programmatically after the document loads (data-for + // can't generate the per-button chips), and re-wired on each rebuild. A scan + // is armed when a chip is clicked; the SDL loop then swallows the next keydown + // and resolves it (Esc cancels). Bindings save to keybinds.ini immediately on + // capture. Mirrors snesrecomp's launcher Configure view. + Rml::ElementDocument* kbdoc = nullptr; // set after LoadDocument + std::function build_rebind_list; // set after LoadDocument + bool rebuild_pending = false; + int scan_kind = 0; // 0=idle, 1=capturing + int scan_index = 0; // button being rebound + std::string scan_chip_id; + + auto kb_chip_label = [&m](int button) -> std::string { + SDL_Scancode sc = psx_keybinds_get_button(m.cfg_player + 1, button); + const char* n = (sc != SDL_SCANCODE_UNKNOWN) ? SDL_GetScancodeName(sc) : ""; + return (n && n[0]) ? std::string(n) : std::string("None"); + }; + auto end_scan = [&]() { + if (!scan_kind) return; + if (kbdoc) if (Rml::Element* e = kbdoc->GetElementById(scan_chip_id)) { + e->SetInnerRML(kb_chip_label(scan_index)); + e->SetClass("rb-chip--scan", false); + } + scan_kind = 0; scan_chip_id.clear(); + }; + auto begin_scan = [&](int index, const std::string& chip_id) { + end_scan(); + scan_kind = 1; scan_index = index; scan_chip_id = chip_id; + if (kbdoc) if (Rml::Element* e = kbdoc->GetElementById(chip_id)) { + e->SetInnerRML("Press a key..."); + e->SetClass("rb-chip--scan", true); + } + }; + auto handle_scan_key = [&](const SDL_KeyboardEvent& ke) { + if (ke.keysym.sym == SDLK_ESCAPE) { end_scan(); return; } + const SDL_Scancode sc = ke.keysym.scancode; + // Steal: a key already bound elsewhere (either player) moves here instead + // of silently double-firing. + for (int pl = 1; pl <= 2; pl++) + for (int b = 0; b < psx_keybinds_button_count(); b++) + if (psx_keybinds_get_button(pl, b) == sc && + !(pl == m.cfg_player + 1 && b == scan_index)) + psx_keybinds_set_button(pl, b, SDL_SCANCODE_UNKNOWN); + psx_keybinds_set_button(m.cfg_player + 1, scan_index, sc); + psx_keybinds_save(); + end_scan(); + rebuild_pending = true; // stolen chips refresh too + }; + + c.BindEventCallback("show_controls", + [&m, handle, &end_scan, &rebuild_pending](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + end_scan(); + m.view = "controls"; handle.DirtyVariable("view"); + rebuild_pending = true; + }); + c.BindEventCallback("cfg_player_1", + [&m, handle, &end_scan, &rebuild_pending](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + end_scan(); + m.cfg_player = 0; m.cfg_player_label = "1"; + handle.DirtyVariable("cfg_player"); handle.DirtyVariable("cfg_player_label"); + rebuild_pending = true; + }); + c.BindEventCallback("cfg_player_2", + [&m, handle, &end_scan, &rebuild_pending](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + end_scan(); + m.cfg_player = 1; m.cfg_player_label = "2"; + handle.DirtyVariable("cfg_player"); handle.DirtyVariable("cfg_player_label"); + rebuild_pending = true; + }); + c.BindEventCallback("rebind_reset", + [&m, &end_scan, &rebuild_pending](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + end_scan(); + psx_keybinds_reset_player(m.cfg_player + 1); + psx_keybinds_save(); + rebuild_pending = true; + }); + + c.BindEventCallback("cycle_renderer", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.renderer ^= 1; + refresh_labels(m); + handle.DirtyVariable("renderer_label"); + }); + c.BindEventCallback("cycle_ss", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.supersampling = (m.supersampling % 4) + 1; + handle.DirtyVariable("supersampling"); + }); + c.BindEventCallback("toggle_aa", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.antialiasing = !m.antialiasing; + handle.DirtyVariable("antialiasing"); + }); + c.BindEventCallback("cycle_texfilter", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.texture_filter ^= 1; refresh_labels(m); + handle.DirtyVariable("texfilter_label"); + }); + c.BindEventCallback("cycle_crt", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.crt = (m.crt + 1) % 4; refresh_labels(m); + handle.DirtyVariable("crt_label"); + }); + c.BindEventCallback("cycle_aspect", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.aspect_index = (m.aspect_index + 1) % kNumAspects; refresh_labels(m); + handle.DirtyVariable("aspect_label"); + handle.DirtyVariable("winsize_label"); /* height follows aspect */ + }); + // EXPERIMENTAL widescreen On/Off. On => 16:9 native-wide (aspect_index 1), + // Off => 4:3 (aspect_index 0). Works on BOTH renderers (SW + the GL wide + // compositor), so it is no longer gated on the software renderer. + // + // 21:9 (kAspects[2]) is STUBBED but intentionally hidden: the engine handles + // it (offset / cull / compositor are all aspect-derived), but the parallax + + // far-backdrop pipeline only generates ~16:9 of coverage, so 21:9 voids the + // far background. When that pipeline is widened, promote this 2-state toggle + // to a 3-way Off / 16:9 / 21:9 — the existing cycle_aspect callback already + // cycles aspect_index 0/1/2 and is the scaffold for it. + c.BindEventCallback("toggle_widescreen", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.aspect_index = (m.aspect_index == 1) ? 0 : 1; // 16:9 <-> 4:3 + refresh_labels(m); + handle.DirtyVariable("widescreen"); + handle.DirtyVariable("aspect_label"); + handle.DirtyVariable("winsize_label"); /* height follows aspect */ + }); + c.BindEventCallback("cycle_winsize", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + int i = (winsize_index(m.window_width) + 1) % kNumWinWidths; + m.window_width = kWinWidths[i]; refresh_labels(m); + handle.DirtyVariable("winsize_label"); + }); + c.BindEventCallback("toggle_spu", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.spu_hq = !m.spu_hq; + handle.DirtyVariable("spu_hq"); + }); + c.BindEventCallback("toggle_skip_fmv", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.auto_skip_fmv = !m.auto_skip_fmv; + handle.DirtyVariable("auto_skip_fmv"); + }); + c.BindEventCallback("toggle_turbo_loads", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.turbo_loads = !m.turbo_loads; + handle.DirtyVariable("turbo_loads"); + }); + c.BindEventCallback("toggle_fullscreen", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.fullscreen = !m.fullscreen; + handle.DirtyVariable("fullscreen"); + }); + // Skip launcher: turning OFF is immediate; turning ON opens a confirm modal + // first, so the user learns the --launcher escape hatch before committing. + c.BindEventCallback("toggle_skip_launcher", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + if (m.skip_launcher) { m.skip_launcher = false; handle.DirtyVariable("skip_launcher"); } + else { m.show_skip_modal = true; handle.DirtyVariable("show_skip_modal"); } + }); + c.BindEventCallback("skip_modal_confirm", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.skip_launcher = true; m.show_skip_modal = false; + handle.DirtyVariable("skip_launcher"); handle.DirtyVariable("show_skip_modal"); + }); + c.BindEventCallback("skip_modal_cancel", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.show_skip_modal = false; handle.DirtyVariable("show_skip_modal"); + }); + c.BindEventCallback("browse_bios", + [&m, window, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + std::string p = win_pick_file(window, "Select PlayStation BIOS", + "BIOS image (*.bin;*.rom)\0*.bin;*.rom\0All files (*.*)\0*.*\0\0"); + if (!p.empty()) { + m.bios_path = fs::path(p).generic_string(); + handle.DirtyVariable("bios_path"); + } + }); + auto do_browse_disc = + [&m, window, handle, game_name_s, expected_serial, expected_crc, has_expected_crc] + (Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + std::string p = win_pick_file(window, "Select disc image", + "Disc image (*.cue;*.bin;*.iso)\0*.cue;*.bin;*.iso\0All files (*.*)\0*.*\0\0"); + if (!p.empty()) { + m.disc_path = fs::path(p).generic_string(); + refresh_disc_status(m, game_name_s, expected_serial, expected_crc, has_expected_crc); + for (const char* v : {"disc_path", "disc_file", "disc_region", "disc_serial", + "v_header", "v_crc", "v_verified", + "verdict_title", "verdict_detail", "verdict_state"}) + handle.DirtyVariable(v); + } + }; + c.BindEventCallback("browse_disc", do_browse_disc); + c.BindEventCallback("change_iso", do_browse_disc); + + c.BindEventCallback("show_settings", + [&m, handle, &end_scan](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + end_scan(); + m.view = "settings"; handle.DirtyVariable("view"); + }); + c.BindEventCallback("show_dashboard", + [&m, handle, &end_scan](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + end_scan(); + m.view = "dashboard"; handle.DirtyVariable("view"); + }); + // ---- controller: device dropdown + pad-mode segmented selector ---- + auto dirty_player = [handle](int player) mutable { + const char* v0[] = {"p1_dev_label","p1_status","p1_dot","p1_options","p1_mode"}; + const char* v1[] = {"p2_dev_label","p2_status","p2_dot","p2_options","p2_mode"}; + for (const char* v : (player == 0 ? v0 : v1)) handle.DirtyVariable(v); + }; + // dev_opts is captured by value: the device list is fixed for the launcher + // session (a hot-plug here would require a re-enumerate, deferred). + c.BindEventCallback("open_dd", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList& args) mutable { + const int player = args.empty() ? 0 : (int)args[0].Get(); + const char* key = player == 0 ? "p1" : "p2"; + m.dd_open = (m.dd_open == key) ? Rml::String() : Rml::String(key); + handle.DirtyVariable("dd_open"); + }); + c.BindEventCallback("close_dd", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.dd_open = Rml::String(); handle.DirtyVariable("dd_open"); + }); + c.BindEventCallback("pick_device", + [&m, handle, dev_opts, dirty_player](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList& args) mutable { + if (args.size() < 2) return; + const int player = (int)args[0].Get(); + const int idx = (int)args[1].Get(); + (player == 0 ? m.p1_dev_index : m.p2_dev_index) = idx; + refresh_player(m, player, dev_opts); + m.dd_open = Rml::String(); + dirty_player(player); + handle.DirtyVariable("dd_open"); + }); + // ---- localization: Language cycle button (Settings > LOCALIZATION) ---- + // Matches the Settings-view idiom (Renderer / Screen model cycle toggles): + // each click advances to the next declared language, wrapping around. + c.BindEventCallback("cycle_language", + [&m, handle, langs = game.languages](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + if (langs.empty()) return; + m.lang_index = (m.lang_index + 1) % (int)langs.size(); + refresh_language(m, langs); + handle.DirtyVariable("lang_label"); + }); + // Pad-mode segmented selector: each segment passes its mode (0=hybrid, + // 1=analog, 2=digital) so any mode is one click away. + c.BindEventCallback("set_mode_p1", + [&m, handle, dev_opts, dirty_player](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList& args) mutable { + if (args.empty()) return; + m.p1_mode = (int)args[0].Get(); refresh_player(m, 0, dev_opts); dirty_player(0); + }); + c.BindEventCallback("set_mode_p2", + [&m, handle, dev_opts, dirty_player](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList& args) mutable { + if (args.empty()) return; + m.p2_mode = (int)args[0].Get(); refresh_player(m, 1, dev_opts); dirty_player(1); + }); + /* Analog-stick deadzone, stepped 0..50% (wraps). Applies to both the + * stick->d-pad threshold and the analog centre dead-band. */ + c.BindEventCallback("cycle_deadzone", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.deadzone_pct += 5; + if (m.deadzone_pct > 50) m.deadzone_pct = 0; + handle.DirtyVariable("deadzone_pct"); + }); + c.BindEventCallback("toggle_mc1", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.mc1_enabled = !m.mc1_enabled; handle.DirtyVariable("mc1_enabled"); + }); + c.BindEventCallback("toggle_mc2", + [&m, handle](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { + m.mc2_enabled = !m.mc2_enabled; handle.DirtyVariable("mc2_enabled"); + }); + + auto dirty_mc = [handle](int slot) mutable { + const char* v0[] = {"mc1_name","mc1_size","mc1_used","mc1_foot","mc1_grid"}; + const char* v1[] = {"mc2_name","mc2_size","mc2_used","mc2_foot","mc2_grid"}; + for (const char* v : (slot == 0 ? v0 : v1)) handle.DirtyVariable(v); + }; + auto browse_mc = [&m, window, dirty_mc](int slot) mutable { + std::string p = win_pick_file(window, "Select memory-card image", + "Memory card (*.mcd;*.mc;*.mcr)\0*.mcd;*.mc;*.mcr\0All files (*.*)\0*.*\0\0"); + if (p.empty()) return; + (slot == 0 ? m.mc1_path : m.mc2_path) = fs::path(p).generic_string(); + refresh_memcard(m, slot); + dirty_mc(slot); + }; + auto new_mc = [&m, window, dirty_mc](int slot) mutable { + Rml::String& cur = (slot == 0 ? m.mc1_path : m.mc2_path); + std::string p = win_pick_save_file(window, "Create new memory card", + "Memory card (*.mcd)\0*.mcd\0All files (*.*)\0*.*\0\0", "mcd", + std::string(cur)); + if (p.empty()) return; + if (memcard_format_file(p.c_str()) != 0) return; // I/O failure: leave as-is + cur = fs::path(p).generic_string(); + refresh_memcard(m, slot); + dirty_mc(slot); + }; + c.BindEventCallback("browse_mc1", + [browse_mc](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { browse_mc(0); }); + c.BindEventCallback("browse_mc2", + [browse_mc](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { browse_mc(1); }); + c.BindEventCallback("new_mc1", + [new_mc](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { new_mc(0); }); + c.BindEventCallback("new_mc2", + [new_mc](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) mutable { new_mc(1); }); + c.BindEventCallback("launch", + [&m](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) { m.launch_requested = true; }); + c.BindEventCallback("quit", + [&m](Rml::DataModelHandle, Rml::Event&, const Rml::VariantList&) { m.quit_requested = true; }); + + // ---- Load the document ---- + const fs::path rml = assets / "launcher.rml"; + Rml::ElementDocument* doc = context->LoadDocument(rml.generic_string()); + if (!doc) { + std::fprintf(stderr, "launcher: failed to load %s — booting without launcher\n", + rml.generic_string().c_str()); + Rml::Shutdown(); + RmlGL3::Shutdown(); + return Result::Unavailable; + } + doc->Show(); + + // ---- build the keybind chip list (Controls page) ---- + // data-if only hides the controls view, so #rebind-list exists from load and + // GetElementById finds it even while the dashboard is showing. Each chip is a + // "; + } + html += ""; + } + list->SetInnerRML(html); // destroys prior chips... + kb_listeners.clear(); // ...so dropping their listeners is safe + for (int k = 0; k < n; k++) { + const std::string id = std::string("kb-") + psx_keybinds_button_name(k); + if (Rml::Element* e = doc->GetElementById(id)) { + auto lis = std::make_unique(); + lis->on_click = [&, k, id]() { begin_scan(k, id); }; + e->AddEventListener(Rml::EventId::Click, lis.get()); + kb_listeners.push_back(std::move(lis)); + } + } + }; + build_rebind_list(); + + // ---- Main loop ---- Result result = Result::Quit; bool running = true; - int win_w = 0, win_h = 0; - SDL_GL_GetDrawableSize(window, &win_w, &win_h); - if (win_w <= 0 || win_h <= 0) { win_w = 1280; win_h = 960; } - - // For tracking mouse press/release - bool prev_mouse = false; - while (running) { - SDL_GL_GetDrawableSize(window, &win_w, &win_h); - if (win_w <= 0 || win_h <= 0) { win_w = 1280; win_h = 960; } - - ui.begin_frame(win_w, win_h); - ui.scale *= m.uiscale; - - // ---- input ---- - { int mx,my; SDL_GetMouseState(&mx,&my); ui.mx=(float)mx; ui.my=(float)my; } - ui.mouse_down = (SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; - ui.mouse_pressed = ui.mouse_down && !prev_mouse; - ui.mouse_released = !ui.mouse_down && prev_mouse; - prev_mouse = ui.mouse_down; - std::memset(ui.keys, 0, sizeof(ui.keys)); - SDL_Event ev; while (SDL_PollEvent(&ev)) { + // While a rebind scan is armed, swallow keyboard input (the next + // keydown resolves it; Esc cancels) so it can't leak into RmlUi + // controls. + if (scan_kind && + (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP || ev.type == SDL_TEXTINPUT)) { + if (ev.type == SDL_KEYDOWN) handle_scan_key(ev.key); + continue; + } switch (ev.type) { - case SDL_QUIT: m.quit_requested = true; break; - case SDL_KEYDOWN: - if (ev.key.keysym.scancode < SDL_NUM_SCANCODES) ui.keys[ev.key.keysym.scancode] = true; - // keybind scan capture - if (m.scan_kind) { - if (ev.key.keysym.sym == SDLK_ESCAPE) { m.scan_kind = 0; m.rebuild_pending = true; } - else { - SDL_Scancode sc = ev.key.keysym.scancode; - for (int pl = 1; pl <= 2; pl++) - for (int b = 0; b < psx_keybinds_button_count(); b++) - if (psx_keybinds_get_button(pl, b) == sc && - !(pl == m.cfg_player + 1 && b == m.scan_index)) - psx_keybinds_set_button(pl, b, SDL_SCANCODE_UNKNOWN); - psx_keybinds_set_button(m.cfg_player + 1, m.scan_index, sc); - psx_keybinds_save(); - m.scan_kind = 0; - m.rebuild_pending = true; - } - } - break; - case SDL_MOUSEWHEEL: - ui.scroll_y += ev.wheel.y * ui.sz(30); + case SDL_QUIT: + m.quit_requested = true; break; case SDL_WINDOWEVENT: - if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { SDL_GL_GetDrawableSize(window, &win_w, &win_h); + render_interface.SetViewport(win_w, win_h); + context->SetDimensions(Rml::Vector2i(win_w, win_h)); + } + RmlSDL::InputEventHandler(context, ev); + break; + default: + RmlSDL::InputEventHandler(context, ev); break; - default: break; } } - // ---- quit / launch signals ---- if (m.launch_requested) { result = Result::Launch; running = false; } if (m.quit_requested) { result = Result::Quit; running = false; } - if (m.scan_kind) { - // if scanning for a keybind, don't process normal UI - // just keep rendering the background - } - // ---- full render every frame (even scan mode) ---- - // We need to implement the view switching and draw everything here. - // Since we can't use closures/event-bindings like RmlUi, we inline - // the view drawing into the loop using switch on m.view. + // Deferred chip-list rebuild (set from chip handlers / scan capture / + // player switch / reset — never rebuild a list from inside its own + // listener's dispatch). + if (rebuild_pending) { rebuild_pending = false; build_rebind_list(); } - // Clear - glViewport(0, 0, win_w, win_h); - glClearColor(THEME_BG.r, THEME_BG.g, THEME_BG.b, 1); - glClear(GL_COLOR_BUFFER_BIT); - gl_ensure_init(); + context->Update(); - const float s = ui.scale; - const float W = (float)win_w, H = (float)win_h; - const float pad = ui.sz(22); - - // ---- background ---- - ui.rect(0, 0, W, H, THEME_BG); - - // ---- common top bar ---- - float logo_sz = ui.sz(46); - if (ui.logo.t) gl_draw_quad(pad, pad, logo_sz, logo_sz, ui.logo.t, {1,1,1,1}); - float tb_tx = pad + logo_sz + ui.sz(16); - ui.font.text(tb_tx, pad + (logo_sz - ui.font.baseline*ui.ts(1.2f))/2, (game_name_s + " Recomp").c_str(), THEME_TEXT, ui.ts(1.2f)); - ui.font.text(tb_tx, pad + logo_sz - ui.sz(16), (game_name_s + " launcher").c_str(), THEME_TEXT_DIM, ui.ts(0.7f)); - - // top-right buttons - float tb_btn_h = ui.sz(30); - float tb_btn_y = pad + (logo_sz - tb_btn_h)/2; - float bw_back = ui.font.width("Back", ui.ts(0.6f)) + ui.sz(24); - float bw_ctl = ui.font.width("Controls", ui.ts(0.6f)) + ui.sz(24); - float bw_set = ui.font.width("Settings", ui.ts(0.6f)) + ui.sz(24); - float bw_launch = ui.font.width("LAUNCH", ui.ts(0.8f)) + ui.sz(40); - - if (m.view == "dashboard") { - // Controls button - float cx = W - pad - bw_launch - ui.sz(16) - bw_set - ui.sz(8) - bw_ctl; - if (ui.button(ui.alloc_id(), cx, tb_btn_y, bw_ctl, tb_btn_h)) m.view = "controls"; - ui.font.text(cx + (bw_ctl - ui.font.width("Controls", ui.ts(0.6f)))/2, - tb_btn_y + (tb_btn_h - ui.font.baseline*ui.ts(0.6f))/2, - "Controls", THEME_TEXT, ui.ts(0.6f)); - // Settings button - cx = W - pad - bw_launch - ui.sz(16) - bw_set; - if (ui.button(ui.alloc_id(), cx, tb_btn_y, bw_set, tb_btn_h)) m.view = "settings"; - ui.font.text(cx + (bw_set - ui.font.width("Settings", ui.ts(0.6f)))/2, - tb_btn_y + (tb_btn_h - ui.font.baseline*ui.ts(0.6f))/2, - "Settings", THEME_TEXT, ui.ts(0.6f)); - } else { - // Back button - float cx = W - pad - bw_back; - if (ui.button(ui.alloc_id(), cx, tb_btn_y, bw_back, tb_btn_h)) m.view = "dashboard"; - ui.font.text(cx + (bw_back - ui.font.width("Back", ui.ts(0.6f)))/2, - tb_btn_y + (tb_btn_h - ui.font.baseline*ui.ts(0.6f))/2, - "Back", THEME_TEXT, ui.ts(0.6f)); - } - - float y = pad + logo_sz + ui.sz(13); - - // ---- main content based on view ---- - if (m.view == "dashboard") { - // scrollable content: from y to above footer - float disc_h = ui.sz(220), card_h = ui.sz(190), mc_h = ui.sz(190); - float gap_s = ui.sz(13); - float base_h = disc_h + gap_s + 2*(card_h+gap_s) + 2*(mc_h+gap_s) + gap_s; - float dash_top = y, dash_bot = H - pad - ui.sz(20) - ui.sz(5); - float avail = dash_bot - dash_top; - // clamp scroll - float max_s = avail - base_h; - if (max_s > 0) max_s = 0; - if (ui.scroll_y > 0) ui.scroll_y = 0; - if (ui.scroll_y < max_s) ui.scroll_y = max_s; - // clip - if (avail > 0) { - glEnable(GL_SCISSOR_TEST); - glScissor(0, win_h-(int)dash_bot, win_w, (int)avail); - } - - render_dashboard(ui, m, dev_opts, window, game_name_s, expected_serial, expected_crc, has_expected_crc, game.languages); - - if (avail > 0) glDisable(GL_SCISSOR_TEST); - - // footer - float footer_y = H - pad - ui.sz(20); - ui.rect(0, footer_y, W, 1, THEME_BORDER); - float skip_y = footer_y + ui.sz(10); - float tog_sz = ui.sz(42), tog_h = ui.sz(22); - if (ui.toggle(ui.alloc_id(), pad, skip_y, tog_sz, tog_h, m.skip_launcher)) { - if (m.skip_launcher) m.skip_launcher = false; - else m.show_skip_modal = true; - } - ui.font.text(pad + ui.sz(50), skip_y + (tog_h - ui.font.baseline*ui.ts(0.6f))/2, "Skip launcher", THEME_TEXT_DIM, ui.ts(0.6f)); - ui.font.text(W/2 - ui.sz(50), skip_y + (tog_h - ui.font.baseline*ui.ts(0.55f))/2, "Recompiler ready", THEME_GREEN, ui.ts(0.55f)); - float l_x = W - pad - bw_launch; - float btn_h = ui.sz(30); - if (ui.button(ui.alloc_id(), l_x, skip_y, bw_launch, btn_h)) m.launch_requested = true; - ui.font.text(l_x + (bw_launch - ui.font.width("LAUNCH", ui.ts(0.8f)))/2, - skip_y + (btn_h - ui.font.baseline*ui.ts(0.8f))/2, - "LAUNCH", THEME_TEXT, ui.ts(0.8f)); - - // skip launcher confirm modal - if (m.show_skip_modal) { - ui.rect(0, 0, W, H, {0,0,0,0.8f}); - float mw = ui.sz(460), mh = ui.sz(160), mx = (W-mw)/2, my = (H-mh)/2; - ui.rect(mx, my, mw, mh, THEME_PANEL); - ui.rect(mx, my, mw, 1, THEME_BORDER); - float mpad = ui.sz(24); - ui.font.text(mx+mpad, my+ui.sz(22), "Skip the launcher on boot?", THEME_TEXT, ui.ts(0.9f)); - ui.font.text(mx+mpad, my+ui.sz(52), "The launcher will no longer appear. Run with --launcher to get it back.", THEME_TEXT_DIM, ui.ts(0.6f)); - float btn_w = ui.sz(100), bbtn_h = ui.sz(30); - if (ui.button(ui.alloc_id(), mx+mw-mpad-btn_w-ui.sz(10)-btn_w, my+mh-mpad-bbtn_h, btn_w, bbtn_h)) m.show_skip_modal = false; - ui.font.text(mx+mw-mpad-btn_w-ui.sz(10)-btn_w + (btn_w - ui.font.width("Cancel",ui.ts(0.6f)))/2, - my+mh-mpad-bbtn_h + (bbtn_h - ui.font.baseline*ui.ts(0.6f))/2, "Cancel", THEME_TEXT, ui.ts(0.6f)); - if (ui.button(ui.alloc_id(), mx+mw-mpad-btn_w, my+mh-mpad-bbtn_h, btn_w, bbtn_h)) { m.skip_launcher = true; m.show_skip_modal = false; } - ui.font.text(mx+mw-mpad-btn_w + (btn_w - ui.font.width("Confirm",ui.ts(0.6f)))/2, - my+mh-mpad-bbtn_h + (bbtn_h - ui.font.baseline*ui.ts(0.6f))/2, "Confirm", THEME_TEXT, ui.ts(0.6f)); - } - } - else if (m.view == "settings") { - // ---- SETTINGS VIEW (scrollable) ---- - float gap_s = ui.sz(13); - const float col_w = (W - 2*pad - gap_s) / 2; - float sx = pad, sy = y; - float mgn = ui.sz(18), btn_pad = ui.sz(24); - - // estimate total height to know scroll range - float rrh = ui.sz(28); - int num_video = 11; // window/renderer/ss/aa/texfilter/crt/fmv/turbo/full/ws/uiscale - float vh = ui.sz(40) + num_video * rrh + ui.sz(10); - float rh_col = vh; // left column - float panel_h = ui.sz(60), ph = ui.sz(28); - float rh_right = (m.lang_menu ? ui.sz(68) : 0) + ui.sz(68) + ui.sz(68); - float sys_h = ui.sz(110); - float total_h = std::max(vh, rh_right) + gap_s + sys_h; - float avail_h = H - sy - pad; - float settings_top = sy, settings_bot = sy + total_h; - if (settings_bot > H - pad) settings_bot = H - pad; - // clip - float clip_h = H - sy - pad; - if (clip_h > 0) { - glEnable(GL_SCISSOR_TEST); - glScissor(0, win_h-(int)(sy+clip_h), win_w, (int)clip_h); - } - - float soff = ui.scroll_y; - // keep scroll in bounds - if (soff > 0) soff = 0; - float max_soff = -(total_h - clip_h); - if (max_soff > 0) max_soff = 0; - if (soff < max_soff) soff = max_soff; - ui.scroll_y = soff; - - sy += soff; - - // left column: VIDEO - ui.rect(sx, sy, col_w, vh, THEME_PANEL); ui.rect(sx, sy, col_w, 1, THEME_BORDER); - ui.font.text(sx+mgn, sy+ui.sz(14), "VIDEO", THEME_ACCENT, ui.ts(0.6f)); - auto setting = [&](const char* label, const std::string& value, int id, - std::function onclick) { - float ry = sy + ui.sz(40) + id*rrh; - if (onclick) { - float bw = ui.font.width(value.c_str(), ui.ts(0.65f)) + btn_pad; - float bx = sx+col_w-mgn-bw; - ui.font.text(sx+mgn, ry + (rrh - ui.font.baseline*ui.ts(0.55f))/2, label, THEME_TEXT_DIM, ui.ts(0.55f)); - if (ui.button(ui.alloc_id(), bx, ry, bw, rrh)) onclick(); - ui.font.text(bx + (bw - ui.font.width(value.c_str(), ui.ts(0.65f)))/2, - ry + (rrh - ui.font.baseline*ui.ts(0.65f))/2, - value.c_str(), THEME_TEXT, ui.ts(0.65f)); - } else { - ui.font.text(sx+mgn, ry + (rrh - ui.font.baseline*ui.ts(0.55f))/2, label, THEME_TEXT_DIM, ui.ts(0.55f)); - } - }; - int ri=0; - setting("Window size", m.winsize_label, ri++, [&](){ - int i=(winsize_index(m.window_width)+1)%kNumWinWidths; m.window_width=kWinWidths[i]; refresh_labels(m); - }); - setting("Renderer", m.renderer_label, ri++, [&](){ m.renderer^=1; refresh_labels(m); }); - setting("Supersampling", std::to_string(m.supersampling)+"x", ri++, [&](){ m.supersampling=(m.supersampling%4)+1; }); - setting("Antialiasing", m.antialiasing?"On":"Off", ri++, [&](){ m.antialiasing=!m.antialiasing; }); - setting("Texture filter", m.texfilter_label, ri++, [&](){ m.texture_filter^=1; refresh_labels(m); }); - setting("Screen model", m.crt_label, ri++, [&](){ m.crt=(m.crt+1)%4; refresh_labels(m); }); - setting("Skip FMVs", m.auto_skip_fmv?"On":"Off", ri++, [&](){ m.auto_skip_fmv=!m.auto_skip_fmv; }); - setting("Turbo loads", m.turbo_loads?"On":"Off", ri++, [&](){ m.turbo_loads=!m.turbo_loads; }); - setting("Fullscreen", m.fullscreen?"On":"Off", ri++, [&](){ m.fullscreen=!m.fullscreen; }); - if (m.ws_offered) - setting("Widescreen", m.widescreen?"On":"Off", ri++, [&](){ m.aspect_index=(m.aspect_index==1)?0:1; refresh_labels(m); }); - // UI scale cycling: 0.5 0.75 1.0 1.25 1.5 1.75 2.0 - static const float kScales[] = {0.5f,0.75f,1.0f,1.25f,1.5f,1.75f,2.0f}; - static const int kNumScales = 7; - m.uiscale_label = std::to_string((int)(m.uiscale*100))+"%"; - setting("UI scale", m.uiscale_label, ri++, [&](){ - int idx = 0; - for (int i=0;i50)m.deadzone_pct=0; } - ui.font.text(rcx+mgn, cy+ui.sz(40)+(ph-ui.font.baseline*ui.ts(0.55f))/2, "Analog stick deadzone", THEME_TEXT_DIM, ui.ts(0.55f)); - ui.font.text(rcx+col_w-mgn-bw + (bw-ui.font.width(dz.c_str(), ui.ts(0.65f)))/2, - cy+ui.sz(40)+(ph-ui.font.baseline*ui.ts(0.65f))/2, dz.c_str(), THEME_TEXT, ui.ts(0.65f)); } - // SYSTEM (full width below columns) - float sys_h_u = ui.sz(110); - float sys_y = sy + std::max(vh, cy+ui.sz(68)) + gap_s; - ui.rect(pad, sys_y, W-2*pad, sys_h_u, THEME_PANEL); ui.rect(pad, sys_y, W-2*pad, 1, THEME_BORDER); - ui.font.text(pad+mgn, sys_y+ui.sz(14), "SYSTEM", THEME_ACCENT, ui.ts(0.6f)); - // BIOS row - float bsy = sys_y + ui.sz(40); - ui.font.text(pad+mgn, bsy+(ph-ui.font.baseline*ui.ts(0.55f))/2, "BIOS", THEME_TEXT_DIM, ui.ts(0.55f)); - float bbw=ui.sz(60); - if (ui.button(ui.alloc_id(), pad+col_w, bsy, bbw, ph)) { - std::string p=win_pick_file(window,"Select PlayStation BIOS","BIOS image (*.bin;*.rom)\0*.bin;*.rom\0All files (*.*)\0*.*\0\0"); - if (!p.empty()) m.bios_path=fs::path(p).generic_string(); - } ui.font.text(pad+col_w+(bbw-ui.font.width("Browse", ui.ts(0.55f)))/2, bsy+(ph-ui.font.baseline*ui.ts(0.55f))/2, "Browse", THEME_TEXT, ui.ts(0.55f)); - std::string bp=m.bios_path.empty()?"(not set)":m.bios_path; - ui.font.text(pad+col_w+bbw+ui.sz(10), bsy+(ph-ui.font.baseline*ui.ts(0.55f))/2, bp.c_str(), m.bios_path.empty()?THEME_RED:THEME_TEXT_MUTED, ui.ts(0.55f)); - // Disc row - float dsy=bsy+ui.sz(32); - ui.font.text(pad+mgn, dsy+(ph-ui.font.baseline*ui.ts(0.55f))/2, "Disc", THEME_TEXT_DIM, ui.ts(0.55f)); - if (ui.button(ui.alloc_id(), pad+col_w, dsy, bbw, ph)) { - std::string p=win_pick_file(window,"Select disc image","Disc image (*.cue;*.bin;*.iso)\0*.cue;*.bin;*.iso\0All files (*.*)\0*.*\0\0"); - if (!p.empty()){ m.disc_path=fs::path(p).generic_string(); refresh_disc_status(m,game_name_s,expected_serial,expected_crc,has_expected_crc); } - } ui.font.text(pad+col_w+(bbw-ui.font.width("Browse", ui.ts(0.55f)))/2, dsy+(ph-ui.font.baseline*ui.ts(0.55f))/2, "Browse", THEME_TEXT, ui.ts(0.55f)); - std::string dp=m.disc_path.empty()?"(not set)":m.disc_path; - ui.font.text(pad+col_w+bbw+ui.sz(10), dsy+(ph-ui.font.baseline*ui.ts(0.55f))/2, dp.c_str(), m.disc_path.empty()?THEME_RED:THEME_TEXT_MUTED, ui.ts(0.55f)); - - if (clip_h > 0) glDisable(GL_SCISSOR_TEST); - } - else if (m.view == "controls") { - // ---- CONTROLS VIEW ---- - float ctrl_h = H - y - pad - ui.sz(20); - ui.rect(pad, y, W-2*pad, ctrl_h, THEME_PANEL); ui.rect(pad, y, W-2*pad, 1, THEME_BORDER); - ui.font.text(pad+ui.sz(18), y+ui.sz(14), "KEYBOARD CONTROLS", THEME_ACCENT, ui.ts(0.6f)); - // player selector - float sel_h = ui.sz(24); - float sel_y = y + ui.sz(40); - ui.font.text(pad+ui.sz(18), sel_y + (sel_h - ui.font.baseline*ui.ts(0.55f))/2, "Player", THEME_TEXT_DIM, ui.ts(0.55f)); - float seg_w = ui.sz(80), seg_x = pad+ui.sz(80); - for (int p=0;p<2;p++){ - float sx=seg_x+seg_w*p; - int id=ui.alloc_id(); - bool over=ui.hot(id,sx,sel_y,seg_w,sel_h); - if (ui.mouse_pressed&&over) ui.active_id=id; - bool clk=ui.mouse_released&&ui.active_id==id&&over; - if (ui.mouse_released&&ui.active_id==id) ui.active_id=0; - Color bgc=(m.cfg_player==p)?THEME_SEG_ON:(over?THEME_BTN_HOVER:THEME_SEG_BG); - ui.rect(sx,sel_y,seg_w,sel_h,bgc); - ui.font.text(sx+(seg_w-ui.font.width(p==0?"1":"2", ui.ts(0.6f)))/2, - sel_y+(sel_h-ui.font.baseline*ui.ts(0.6f))/2, p==0?"1":"2", THEME_TEXT, ui.ts(0.6f)); - if (clk){ m.cfg_player=p; m.rebuild_pending=true; } - if (p==0) ui.rect(sx+seg_w-1,sel_y,1,sel_h,THEME_BORDER); - } - // reset button - float rw=ui.sz(120); - if (ui.button(ui.alloc_id(), W-pad-ui.sz(18)-rw, sel_y, rw, sel_h)) { - psx_keybinds_reset_player(m.cfg_player+1); psx_keybinds_save(); m.rebuild_pending=true; - } - ui.font.text(W-pad-ui.sz(18)-rw+(rw-ui.font.width("Reset to defaults", ui.ts(0.55f)))/2, - sel_y+(sel_h-ui.font.baseline*ui.ts(0.55f))/2, "Reset to defaults", THEME_TEXT, ui.ts(0.55f)); - // keybind list (scrollable) - float lb_y = sel_y + sel_h + ui.sz(12); - float row_h_kb = ui.sz(26), chip_w = ui.sz(120), chip_x = pad+ui.sz(150); - int n = psx_keybinds_button_count(); - // apply scroll offset and clip (scissor in window-pixel coords) - float content_top = lb_y, content_bot = y + ctrl_h; - if (content_bot > content_top) { - glEnable(GL_SCISSOR_TEST); - glScissor((int)pad, win_h-(int)content_bot, (int)(W-2*pad), (int)(content_bot-content_top)); - } - float ry_base = lb_y + ui.scroll_y; - for (int i=0;i content_bot) break; - const char* lbl = psx_keybinds_button_label(i); - ui.font.text(pad+ui.sz(22), ry + (row_h_kb - ui.font.baseline*ui.ts(0.5f))/2, lbl, THEME_TEXT, ui.ts(0.5f)); - SDL_Scancode sc = psx_keybinds_get_button(m.cfg_player+1, i); - const char* key_name = (sc != SDL_SCANCODE_UNKNOWN) ? SDL_GetScancodeName(sc) : "None"; - if (m.scan_kind && m.scan_index == i) { - (void)key_name; - if (ui.button(ui.alloc_id(), chip_x, ry, chip_w, row_h_kb)) { } - ui.font.text(chip_x + (chip_w - ui.font.width("Press a key...", ui.ts(0.5f)))/2, - ry + (row_h_kb - ui.font.baseline*ui.ts(0.5f))/2, "Press a key...", THEME_WARN, ui.ts(0.5f)); - } else { - if (ui.button(ui.alloc_id(), chip_x, ry, chip_w, row_h_kb)) { - m.scan_kind = 1; m.scan_index = i; - } - float tw3 = ui.font.width(key_name, ui.ts(0.5f)); - ui.font.text(chip_x + (chip_w - tw3)/2, - ry + (row_h_kb - ui.font.baseline*ui.ts(0.5f))/2, - key_name, THEME_TEXT, ui.ts(0.5f)); - } - } - if (content_bot > content_top) glDisable(GL_SCISSOR_TEST); - // clamp scroll to prevent overscroll - float total_h = n * row_h_kb; - float visible_h = content_bot - content_top; - if (total_h > visible_h) { - if (ui.scroll_y > 0) ui.scroll_y = 0; - if (ui.scroll_y < -(total_h - visible_h)) ui.scroll_y = -(total_h - visible_h); - } else ui.scroll_y = 0; - } - - gl_ensure_init(); + render_interface.Clear(); + render_interface.BeginFrame(); + context->Render(); + render_interface.EndFrame(); SDL_GL_SwapWindow(window); } @@ -1169,31 +1254,38 @@ Result run(SDL_Window* window, void* gl_context, io.turbo_loads = m.turbo_loads; io.has_turbo_loads = true; io.fullscreen = m.fullscreen; io.has_fullscreen = true; io.skip_launcher = m.skip_launcher; io.has_skip_launcher = true; - io.spu_hq = m.spu_hq; io.has_spu_hq = true; + io.spu_hq = m.spu_hq; io.has_spu_hq = true; io.aspect_num = kAspects[m.aspect_index][0]; io.aspect_den = kAspects[m.aspect_index][1]; io.has_aspect_ratio = true; io.window_width = m.window_width; io.has_window_width = true; - if (!m.bios_path.empty()) { io.bios_path = fs::path(m.bios_path); io.has_bios_path = true; } - if (!m.disc_path.empty()) { io.disc_path = fs::path(m.disc_path); io.has_disc_path = true; } + if (!m.bios_path.empty()) { io.bios_path = fs::path(std::string(m.bios_path)); io.has_bios_path = true; } + if (!m.disc_path.empty()) { io.disc_path = fs::path(std::string(m.disc_path)); io.has_disc_path = true; } + io.memcard1_enabled = m.mc1_enabled; io.has_memcard1_enabled = true; io.memcard2_enabled = m.mc2_enabled; io.has_memcard2_enabled = true; - if (!m.mc1_path.empty()) { io.memcard1_path = fs::path(m.mc1_path); io.has_memcard1_path = true; } - if (!m.mc2_path.empty()) { io.memcard2_path = fs::path(m.mc2_path); io.has_memcard2_path = true; } - const int i1=m.p1_dev_index>0&&m.p1_dev_index<(int)dev_opts.size()?m.p1_dev_index:0; - const int i2=m.p2_dev_index>0&&m.p2_dev_index<(int)dev_opts.size()?m.p2_dev_index:0; + if (!m.mc1_path.empty()) { io.memcard1_path = fs::path(std::string(m.mc1_path)); io.has_memcard1_path = true; } + if (!m.mc2_path.empty()) { io.memcard2_path = fs::path(std::string(m.mc2_path)); io.has_memcard2_path = true; } + + const int i1 = (m.p1_dev_index >= 0 && m.p1_dev_index < (int)dev_opts.size()) ? m.p1_dev_index : 0; + const int i2 = (m.p2_dev_index >= 0 && m.p2_dev_index < (int)dev_opts.size()) ? m.p2_dev_index : 0; io.p1_device = device_string(dev_opts[i1]); io.has_p1_device = true; io.p2_device = device_string(dev_opts[i2]); io.has_p2_device = true; io.p1_mode = m.p1_mode; io.has_p1_mode = true; io.p2_mode = m.p2_mode; io.has_p2_mode = true; io.deadzone = m.deadzone_pct * 32767 / 100; io.has_deadzone = true; - if (m.lang_menu && m.lang_index >= 0 && m.lang_index < (int)game.languages.size()) { + + // Localization: persist the chosen language code (only meaningful when the + // game declared a menu; otherwise leave io.language untouched). + if (m.lang_menu && m.lang_index >= 0 && + m.lang_index < (int)game.languages.size()) { io.language = game.languages[m.lang_index].code; io.has_language = true; } - // io.uiscale = m.uiscale; io.has_uiscale = true; } + Rml::Shutdown(); + RmlGL3::Shutdown(); return result; } diff --git a/runtime/launcher/launcher.h b/runtime/launcher/launcher.h index 268cbf68..75083b39 100644 --- a/runtime/launcher/launcher.h +++ b/runtime/launcher/launcher.h @@ -1,13 +1,15 @@ -// launcher.h — SDL2/OpenGL launcher front-end. +// launcher.h — integrated RmlUi launcher front-end. // -// Shown in a temporary SDL window before the emulator boots: the user -// picks renderer / supersampling / AA / colour model / SPU-HQ, the BIOS, -// disc, memory cards and controllers, then presses LAUNCH. The chosen -// values are written back into the UserSettings the runtime then applies -// (and persisted to settings.toml by the caller). +// Shown in the runtime's SDL/OpenGL window before the emulator boots: the user +// picks renderer / supersampling / AA / colour model / SPU-HQ, the BIOS, disc, +// memory cards and controllers, then presses LAUNCH. The chosen values are +// written back into the UserSettings the runtime then applies (and persisted to +// settings.toml by the caller). // -// This module creates its own SDL window + GL 3.3 core context — the -// caller creates the window, calls run(), then tears everything down. +// Design note — the launcher does NOT create or own the window or GL context; +// the caller passes an already-current GL 3.3 context. This keeps the module a +// pure overlay so a future "re-open settings while the game is running" path can +// reuse it without owning the window lifecycle. #pragma once @@ -22,27 +24,37 @@ namespace PSXRecompV4 { struct UserSettings; } namespace psx_launcher { enum class Result { - Launch, // user pressed LAUNCH — proceed to boot with `io` - Quit, // user closed the window — caller should exit - Unavailable, // launcher could not initialise (assets/GL); caller boots as if skipped + Launch, // user pressed LAUNCH — proceed to boot with `io` + Quit, // user closed the window — caller should exit + Unavailable, // launcher could not initialise (assets/GL); caller boots as if skipped }; +// Static facts about the game the launcher is configuring. Drives the title and +// the disc-verification badge. Extends naturally for later phases. struct GameInfo { - const char* name = nullptr; - const char* expected_serial = nullptr; - uint32_t expected_crc = 0; - bool has_expected_crc = false; - bool allow_hybrid = true; - bool lock_mode = false; - int locked_mode = 2; // PAD_MODE_DIGITAL - - bool lock_device = false; // true hides the Player controller-card device selector - bool ws_offered = true; // false hides the widescreen toggle + clamps to 4:3 + const char* name = nullptr; // display name, e.g. "Tomba!" + const char* expected_serial = nullptr; // game id "SCUS-94236" (null = no serial check) + uint32_t expected_crc = 0; // full-file CRC32 of the data track + bool has_expected_crc = false; // whether expected_crc is meaningful + bool allow_hybrid = true; // offer the "Hybrid" pad mode (false => Analog | D-Pad only) + bool lock_mode = false; // hide the whole pad-mode selector and force locked_mode (single-pad-type games) + int locked_mode = 2; // PAD_MODE_DIGITAL; the mode forced when lock_mode is true + bool lock_device = false; // hide the Player 1/2 controller cards entirely (fixed, auto-bound pad type; e.g. Ape Escape DualShock) + bool ws_offered = true; // offer the EXPERIMENTAL Widescreen toggle (false = hidden, game ships 4:3 only) + // Optional "Localization" dropdown. Populated from game.toml + // [localization].languages. When non-empty the launcher shows a language + // menu; empty => no menu (the general case — only Tsumu declares languages). + // code feeds the runtime's translation layer ("off"/"jp"/"" = untranslated). struct Language { std::string code; std::string label; }; std::vector languages; }; +// Run the launcher loop to completion. `gl_context` is an SDL_GLContext (void* +// to avoid leaking SDL types into this header) already created and current on +// `window`. `io` is seeded with the effective settings (game.toml ∪ settings.toml) +// and, on Result::Launch, updated in place with the user's choices. `assets_dir` +// is the directory holding launcher.rml / .rcss / fonts. Result run(SDL_Window* window, void* gl_context, PSXRecompV4::UserSettings& io, const GameInfo& game, const char* assets_dir); diff --git a/runtime/launcher/stb_image_impl.cpp b/runtime/launcher/stb_image_impl.cpp index 31c4650c..d247ef4f 100644 --- a/runtime/launcher/stb_image_impl.cpp +++ b/runtime/launcher/stb_image_impl.cpp @@ -1,5 +1,6 @@ // stb_image implementation TU. Kept separate so the (large) decoder lives in // its own translation unit and launcher.cpp only sees the declarations. #define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_STDIO // we feed it memory buffers via RmlUi's FileInterface #define STBI_ONLY_PNG // launcher art is PNG; keeps the decoder small #include "third_party/stb_image.h" diff --git a/runtime/launcher/third_party/stb_truetype.h b/runtime/launcher/third_party/stb_truetype.h deleted file mode 100644 index 90a5c2e2..00000000 --- a/runtime/launcher/third_party/stb_truetype.h +++ /dev/null @@ -1,5079 +0,0 @@ -// stb_truetype.h - v1.26 - public domain -// authored from 2009-2021 by Sean Barrett / RAD Game Tools -// -// ======================================================================= -// -// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES -// -// This library does no range checking of the offsets found in the file, -// meaning an attacker can use it to read arbitrary memory. -// -// ======================================================================= -// -// This library processes TrueType files: -// parse files -// extract glyph metrics -// extract glyph shapes -// render glyphs to one-channel bitmaps with antialiasing (box filter) -// render glyphs to one-channel SDF bitmaps (signed-distance field/function) -// -// Todo: -// non-MS cmaps -// crashproof on bad data -// hinting? (no longer patented) -// cleartype-style AA? -// optimize: use simple memory allocator for intermediates -// optimize: build edge-list directly from curves -// optimize: rasterize directly from curves? -// -// ADDITIONAL CONTRIBUTORS -// -// Mikko Mononen: compound shape support, more cmap formats -// Tor Andersson: kerning, subpixel rendering -// Dougall Johnson: OpenType / Type 2 font handling -// Daniel Ribeiro Maciel: basic GPOS-based kerning -// -// Misc other: -// Ryan Gordon -// Simon Glass -// github:IntellectualKitty -// Imanol Celaya -// Daniel Ribeiro Maciel -// -// Bug/warning reports/fixes: -// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe -// Cass Everitt Martins Mozeiko github:aloucks -// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam -// Brian Hook Omar Cornut github:vassvik -// Walter van Niftrik Ryan Griege -// David Gow Peter LaValle -// David Given Sergey Popov -// Ivan-Assen Ivanov Giumo X. Clanjor -// Anthony Pesch Higor Euripedes -// Johan Duparc Thomas Fields -// Hou Qiming Derek Vinyard -// Rob Loach Cort Stratton -// Kenney Phillis Jr. Brian Costabile -// Ken Voskuil (kaesve) Yakov Galka -// -// VERSION HISTORY -// -// 1.26 (2021-08-28) fix broken rasterizer -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// variant PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// -// Full history can be found at the end of this file. -// -// LICENSE -// -// See end of file for license information. -// -// USAGE -// -// Include this file in whatever places need to refer to it. In ONE C/C++ -// file, write: -// #define STB_TRUETYPE_IMPLEMENTATION -// before the #include of this file. This expands out the actual -// implementation into that C/C++ file. -// -// To make the implementation private to the file that generates the implementation, -// #define STBTT_STATIC -// -// Simple 3D API (don't ship this, but it's fine for tools and quick start) -// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture -// stbtt_GetBakedQuad() -- compute quad to draw for a given char -// -// Improved 3D API (more shippable): -// #include "stb_rect_pack.h" -- optional, but you really want it -// stbtt_PackBegin() -// stbtt_PackSetOversampling() -- for improved quality on small fonts -// stbtt_PackFontRanges() -- pack and renders -// stbtt_PackEnd() -// stbtt_GetPackedQuad() -// -// "Load" a font file from a memory buffer (you have to keep the buffer loaded) -// stbtt_InitFont() -// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections -// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections -// -// Render a unicode codepoint to a bitmap -// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap -// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide -// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be -// -// Character advance/positioning -// stbtt_GetCodepointHMetrics() -// stbtt_GetFontVMetrics() -// stbtt_GetFontVMetricsOS2() -// stbtt_GetCodepointKernAdvance() -// -// Starting with version 1.06, the rasterizer was replaced with a new, -// faster and generally-more-precise rasterizer. The new rasterizer more -// accurately measures pixel coverage for anti-aliasing, except in the case -// where multiple shapes overlap, in which case it overestimates the AA pixel -// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If -// this turns out to be a problem, you can re-enable the old rasterizer with -// #define STBTT_RASTERIZER_VERSION 1 -// which will incur about a 15% speed hit. -// -// ADDITIONAL DOCUMENTATION -// -// Immediately after this block comment are a series of sample programs. -// -// After the sample programs is the "header file" section. This section -// includes documentation for each API function. -// -// Some important concepts to understand to use this library: -// -// Codepoint -// Characters are defined by unicode codepoints, e.g. 65 is -// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is -// the hiragana for "ma". -// -// Glyph -// A visual character shape (every codepoint is rendered as -// some glyph) -// -// Glyph index -// A font-specific integer ID representing a glyph -// -// Baseline -// Glyph shapes are defined relative to a baseline, which is the -// bottom of uppercase characters. Characters extend both above -// and below the baseline. -// -// Current Point -// As you draw text to the screen, you keep track of a "current point" -// which is the origin of each character. The current point's vertical -// position is the baseline. Even "baked fonts" use this model. -// -// Vertical Font Metrics -// The vertical qualities of the font, used to vertically position -// and space the characters. See docs for stbtt_GetFontVMetrics. -// -// Font Size in Pixels or Points -// The preferred interface for specifying font sizes in stb_truetype -// is to specify how tall the font's vertical extent should be in pixels. -// If that sounds good enough, skip the next paragraph. -// -// Most font APIs instead use "points", which are a common typographic -// measurement for describing font size, defined as 72 points per inch. -// stb_truetype provides a point API for compatibility. However, true -// "per inch" conventions don't make much sense on computer displays -// since different monitors have different number of pixels per -// inch. For example, Windows traditionally uses a convention that -// there are 96 pixels per inch, thus making 'inch' measurements have -// nothing to do with inches, and thus effectively defining a point to -// be 1.333 pixels. Additionally, the TrueType font data provides -// an explicit scale factor to scale a given font's glyphs to points, -// but the author has observed that this scale factor is often wrong -// for non-commercial fonts, thus making fonts scaled in points -// according to the TrueType spec incoherently sized in practice. -// -// DETAILED USAGE: -// -// Scale: -// Select how high you want the font to be, in points or pixels. -// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute -// a scale factor SF that will be used by all other functions. -// -// Baseline: -// You need to select a y-coordinate that is the baseline of where -// your text will appear. Call GetFontBoundingBox to get the baseline-relative -// bounding box for all characters. SF*-y0 will be the distance in pixels -// that the worst-case character could extend above the baseline, so if -// you want the top edge of characters to appear at the top of the -// screen where y=0, then you would set the baseline to SF*-y0. -// -// Current point: -// Set the current point where the first character will appear. The -// first character could extend left of the current point; this is font -// dependent. You can either choose a current point that is the leftmost -// point and hope, or add some padding, or check the bounding box or -// left-side-bearing of the first character to be displayed and set -// the current point based on that. -// -// Displaying a character: -// Compute the bounding box of the character. It will contain signed values -// relative to . I.e. if it returns x0,y0,x1,y1, -// then the character should be displayed in the rectangle from -// to = 32 && *text < 128) { - stbtt_aligned_quad q; - stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 - glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); - glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); - glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); - glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); - } - ++text; - } - glEnd(); -} -#endif -// -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program (this compiles): get a single bitmap, print as ASCII art -// -#if 0 -#include -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<25]; - -int main(int argc, char **argv) -{ - stbtt_fontinfo font; - unsigned char *bitmap; - int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); - - fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); - - stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); - bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); - - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) - putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); - putchar('\n'); - } - return 0; -} -#endif -// -// Output: -// -// .ii. -// @@@@@@. -// V@Mio@@o -// :i. V@V -// :oM@@M -// :@@@MM@M -// @@o o@M -// :@@. M@M -// @@@o@@@@ -// :M@@V:@@. -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program: print "Hello World!" banner, with bugs -// -#if 0 -char buffer[24<<20]; -unsigned char screen[20][79]; - -int main(int arg, char **argv) -{ - stbtt_fontinfo font; - int i,j,ascent,baseline,ch=0; - float scale, xpos=2; // leave a little padding in case the character extends left - char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness - - fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); - stbtt_InitFont(&font, buffer, 0); - - scale = stbtt_ScaleForPixelHeight(&font, 15); - stbtt_GetFontVMetrics(&font, &ascent,0,0); - baseline = (int) (ascent*scale); - - while (text[ch]) { - int advance,lsb,x0,y0,x1,y1; - float x_shift = xpos - (float) floor(xpos); - stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); - stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); - stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); - // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong - // because this API is really for baking character bitmaps into textures. if you want to render - // a sequence of characters, you really need to render each bitmap to a temp buffer, then - // "alpha blend" that into the working buffer - xpos += (advance * scale); - if (text[ch+1]) - xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); - ++ch; - } - - for (j=0; j < 20; ++j) { - for (i=0; i < 78; ++i) - putchar(" .:ioVM@"[screen[j][i]>>5]); - putchar('\n'); - } - - return 0; -} -#endif - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// INTEGRATION WITH YOUR CODEBASE -//// -//// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype, e.g. if you don't -//// link with the C runtime library. - -#ifdef STB_TRUETYPE_IMPLEMENTATION - // #define your own (u)stbtt_int8/16/32 before including to override this - #ifndef stbtt_uint8 - typedef unsigned char stbtt_uint8; - typedef signed char stbtt_int8; - typedef unsigned short stbtt_uint16; - typedef signed short stbtt_int16; - typedef unsigned int stbtt_uint32; - typedef signed int stbtt_int32; - #endif - - typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; - typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; - - // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h - #ifndef STBTT_ifloor - #include - #define STBTT_ifloor(x) ((int) floor(x)) - #define STBTT_iceil(x) ((int) ceil(x)) - #endif - - #ifndef STBTT_sqrt - #include - #define STBTT_sqrt(x) sqrt(x) - #define STBTT_pow(x,y) pow(x,y) - #endif - - #ifndef STBTT_fmod - #include - #define STBTT_fmod(x,y) fmod(x,y) - #endif - - #ifndef STBTT_cos - #include - #define STBTT_cos(x) cos(x) - #define STBTT_acos(x) acos(x) - #endif - - #ifndef STBTT_fabs - #include - #define STBTT_fabs(x) fabs(x) - #endif - - // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h - #ifndef STBTT_malloc - #include - #define STBTT_malloc(x,u) ((void)(u),malloc(x)) - #define STBTT_free(x,u) ((void)(u),free(x)) - #endif - - #ifndef STBTT_assert - #include - #define STBTT_assert(x) assert(x) - #endif - - #ifndef STBTT_strlen - #include - #define STBTT_strlen(x) strlen(x) - #endif - - #ifndef STBTT_memcpy - #include - #define STBTT_memcpy memcpy - #define STBTT_memset memset - #endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// INTERFACE -//// -//// - -#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ -#define __STB_INCLUDE_STB_TRUETYPE_H__ - -#ifdef STBTT_STATIC -#define STBTT_DEF static -#else -#define STBTT_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// private structure -typedef struct -{ - unsigned char *data; - int cursor; - int size; -} stbtt__buf; - -////////////////////////////////////////////////////////////////////////////// -// -// TEXTURE BAKING API -// -// If you use this API, you only have to call two functions ever. -// - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; -} stbtt_bakedchar; - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata); // you allocate this, it's num_chars long -// if return is positive, the first unused row of the bitmap -// if return is negative, returns the negative of the number of characters that fit -// if return is 0, no characters fit and no rows were used -// This uses a very crappy packing. - -typedef struct -{ - float x0,y0,s0,t0; // top-left - float x1,y1,s1,t1; // bottom-right -} stbtt_aligned_quad; - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier -// Call GetBakedQuad with char_index = 'character - first_char', and it -// creates the quad you need to draw and advances the current position. -// -// The coordinate system used assumes y increases downwards. -// -// Characters will extend both above and below the current position; -// see discussion of "BASELINE" above. -// -// It's inefficient; you might want to c&p it and optimize it. - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); -// Query the font vertical metrics without having to create a font first. - - -////////////////////////////////////////////////////////////////////////////// -// -// NEW TEXTURE BAKING API -// -// This provides options for packing multiple fonts into one atlas, not -// perfectly but better than nothing. - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; - float xoff2,yoff2; -} stbtt_packedchar; - -typedef struct stbtt_pack_context stbtt_pack_context; -typedef struct stbtt_fontinfo stbtt_fontinfo; -#ifndef STB_RECT_PACK_VERSION -typedef struct stbrp_rect stbrp_rect; -#endif - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); -// Initializes a packing context stored in the passed-in stbtt_pack_context. -// Future calls using this context will pack characters into the bitmap passed -// in here: a 1-channel bitmap that is width * height. stride_in_bytes is -// the distance from one row to the next (or 0 to mean they are packed tightly -// together). "padding" is the amount of padding to leave between each -// character (normally you want '1' for bitmaps you'll use as textures with -// bilinear filtering). -// -// Returns 0 on failure, 1 on success. - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); -// Cleans up the packing context and frees all memory. - -#define STBTT_POINT_SIZE(x) (-(x)) - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); -// Creates character bitmaps from the font_index'th font found in fontdata (use -// font_index=0 if you don't know what that is). It creates num_chars_in_range -// bitmaps for characters with unicode values starting at first_unicode_char_in_range -// and increasing. Data for how to render them is stored in chardata_for_range; -// pass these to stbtt_GetPackedQuad to get back renderable quads. -// -// font_size is the full height of the character from ascender to descender, -// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed -// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() -// and pass that result as 'font_size': -// ..., 20 , ... // font max minus min y is 20 pixels tall -// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall - -typedef struct -{ - float font_size; - int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint - int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints - int num_chars; - stbtt_packedchar *chardata_for_range; // output - unsigned char h_oversample, v_oversample; // don't set these, they're used internally -} stbtt_pack_range; - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); -// Creates character bitmaps from multiple ranges of characters stored in -// ranges. This will usually create a better-packed bitmap than multiple -// calls to stbtt_PackFontRange. Note that you can call this multiple -// times within a single PackBegin/PackEnd. - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); -// Oversampling a font increases the quality by allowing higher-quality subpixel -// positioning, and is especially valuable at smaller text sizes. -// -// This function sets the amount of oversampling for all following calls to -// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given -// pack context. The default (no oversampling) is achieved by h_oversample=1 -// and v_oversample=1. The total number of pixels required is -// h_oversample*v_oversample larger than the default; for example, 2x2 -// oversampling requires 4x the storage of 1x1. For best results, render -// oversampled textures with bilinear filtering. Look at the readme in -// stb/tests/oversample for information about oversampled fonts -// -// To use with PackFontRangesGather etc., you must set it before calls -// call to PackFontRangesGatherRects. - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); -// If skip != 0, this tells stb_truetype to skip any codepoints for which -// there is no corresponding glyph. If skip=0, which is the default, then -// codepoints without a glyph recived the font's "missing character" glyph, -// typically an empty box by convention. - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int align_to_integer); - -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -// Calling these functions in sequence is roughly equivalent to calling -// stbtt_PackFontRanges(). If you more control over the packing of multiple -// fonts, or if you want to pack custom data into a font texture, take a look -// at the source to of stbtt_PackFontRanges() and create a custom version -// using these functions, e.g. call GatherRects multiple times, -// building up a single array of rects, then call PackRects once, -// then call RenderIntoRects repeatedly. This may result in a -// better packing than calling PackFontRanges multiple times -// (or it may not). - -// this is an opaque structure that you shouldn't mess with which holds -// all the context needed from PackBegin to PackEnd. -struct stbtt_pack_context { - void *user_allocator_context; - void *pack_info; - int width; - int height; - int stride_in_bytes; - int padding; - int skip_missing; - unsigned int h_oversample, v_oversample; - unsigned char *pixels; - void *nodes; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// FONT LOADING -// -// - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); -// This function will determine the number of fonts in a font file. TrueType -// collection (.ttc) files may contain multiple fonts, while TrueType font -// (.ttf) files only contain one font. The number of fonts can be used for -// indexing with the previous function where the index is between zero and one -// less than the total fonts. If an error occurs, -1 is returned. - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); -// Each .ttf/.ttc file may have more than one font. Each font has a sequential -// index number starting from 0. Call this function to get the font offset for -// a given index; it returns -1 if the index is out of range. A regular .ttf -// file will only define one font and it always be at offset 0, so it will -// return '0' for index 0, and -1 for all other indices. - -// The following structure is defined publicly so you can declare one on -// the stack or as a global or etc, but you should treat it as opaque. -struct stbtt_fontinfo -{ - void * userdata; - unsigned char * data; // pointer to .ttf file - int fontstart; // offset of start of font - - int numGlyphs; // number of glyphs, needed for range checking - - int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf - int index_map; // a cmap mapping for our chosen character encoding - int indexToLocFormat; // format needed to map from glyph index to glyph - - stbtt__buf cff; // cff font data - stbtt__buf charstrings; // the charstring index - stbtt__buf gsubrs; // global charstring subroutines index - stbtt__buf subrs; // private charstring subroutines index - stbtt__buf fontdicts; // array of font dicts - stbtt__buf fdselect; // map from glyph to fontdict -}; - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); -// Given an offset into the file that defines a font, this function builds -// the necessary cached info for the rest of the system. You must allocate -// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't -// need to do anything special to free it, because the contents are pure -// value data with no additional data structures. Returns 0 on failure. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER TO GLYPH-INDEX CONVERSIOn - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); -// If you're going to perform multiple operations on the same character -// and you want a speed-up, call this function with the character you're -// going to process, then use glyph-based functions instead of the -// codepoint-based functions. -// Returns 0 if the character codepoint is not defined in the font. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER PROPERTIES -// - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose "height" is 'pixels' tall. -// Height is measured as the distance from the highest ascender to the lowest -// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics -// and computing: -// scale = pixels / (ascent - descent) -// so if you prefer to measure height by the ascent only, use a similar calculation. - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose EM size is mapped to -// 'pixels' tall. This is probably what traditional APIs compute, but -// I'm not positive. - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); -// ascent is the coordinate above the baseline the font extends; descent -// is the coordinate below the baseline the font extends (i.e. it is typically negative) -// lineGap is the spacing between one row's descent and the next row's ascent... -// so you should advance the vertical position by "*ascent - *descent + *lineGap" -// these are expressed in unscaled coordinates, so you must multiply by -// the scale factor for a given size - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); -// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 -// table (specific to MS/Windows TTF files). -// -// Returns 1 on success (table present), 0 on failure. - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); -// the bounding box around all possible characters - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); -// leftSideBearing is the offset from the current horizontal position to the left edge of the character -// advanceWidth is the offset from the current horizontal position to the next horizontal position -// these are expressed in unscaled coordinates - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); -// an additional amount to add to the 'advance' value between ch1 and ch2 - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); -// Gets the bounding box of the visible part of the glyph, in unscaled coordinates - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); -// as above, but takes one or more glyph indices for greater efficiency - -typedef struct stbtt_kerningentry -{ - int glyph1; // use stbtt_FindGlyphIndex - int glyph2; - int advance; -} stbtt_kerningentry; - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); -// Retrieves a complete list of all of the kerning pairs provided by the font -// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. -// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) - -////////////////////////////////////////////////////////////////////////////// -// -// GLYPH SHAPES (you probably don't need these, but they have to go before -// the bitmaps for C declaration-order reasons) -// - -#ifndef STBTT_vmove // you can predefine these to use different values (but why?) - enum { - STBTT_vmove=1, - STBTT_vline, - STBTT_vcurve, - STBTT_vcubic - }; -#endif - -#ifndef stbtt_vertex // you can predefine this to use different values - // (we share this with other code at RAD) - #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file - typedef struct - { - stbtt_vertex_type x,y,cx,cy,cx1,cy1; - unsigned char type,padding; - } stbtt_vertex; -#endif - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); -// returns non-zero if nothing is drawn for this glyph - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); -// returns # of vertices and fills *vertices with the pointer to them -// these are expressed in "unscaled" coordinates -// -// The shape is a series of contours. Each one starts with -// a STBTT_moveto, then consists of a series of mixed -// STBTT_lineto and STBTT_curveto segments. A lineto -// draws a line from previous endpoint to its x,y; a curveto -// draws a quadratic bezier from previous endpoint to -// its x,y, using cx,cy as the bezier control point. - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); -// frees the data allocated above - -STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); -// fills svg with the character's SVG data. -// returns data size or 0 if SVG not found. - -////////////////////////////////////////////////////////////////////////////// -// -// BITMAP RENDERING -// - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); -// frees the bitmap allocated below - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// allocates a large-enough single-channel 8bpp bitmap and renders the -// specified character/glyph at the specified scale into it, with -// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). -// *width & *height are filled out with the width & height of the bitmap, -// which is stored left-to-right, top-to-bottom. -// -// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); -// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap -// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap -// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the -// width and height and positioning info for it first. - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); -// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); -// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering -// is performed (see stbtt_PackSetOversampling) - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -// get the bbox of the bitmap centered around the glyph origin; so the -// bitmap width is ix1-ix0, height is iy1-iy0, and location to place -// the bitmap top left is (leftSideBearing*scale,iy0). -// (Note that the bitmap uses y-increases-down, but the shape uses -// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); -// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel -// shift for the character - -// the following functions are equivalent to the above functions, but operate -// on glyph indices instead of Unicode codepoints (for efficiency) -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); - - -// @TODO: don't expose this structure -typedef struct -{ - int w,h,stride; - unsigned char *pixels; -} stbtt__bitmap; - -// rasterize a shape with quadratic beziers into a bitmap -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into - float flatness_in_pixels, // allowable error of curve in pixels - stbtt_vertex *vertices, // array of vertices defining shape - int num_verts, // number of vertices in above array - float scale_x, float scale_y, // scale applied to input vertices - float shift_x, float shift_y, // translation applied to input vertices - int x_off, int y_off, // another translation applied to input - int invert, // if non-zero, vertically flip shape - void *userdata); // context for to STBTT_MALLOC - -////////////////////////////////////////////////////////////////////////////// -// -// Signed Distance Function (or Field) rendering - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); -// frees the SDF bitmap allocated below - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -// These functions compute a discretized SDF field for a single character, suitable for storing -// in a single-channel texture, sampling with bilinear filtering, and testing against -// larger than some threshold to produce scalable fonts. -// info -- the font -// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap -// glyph/codepoint -- the character to generate the SDF for -// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), -// which allows effects like bit outlines -// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) -// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) -// if positive, > onedge_value is inside; if negative, < onedge_value is inside -// width,height -- output height & width of the SDF bitmap (including padding) -// xoff,yoff -- output origin of the character -// return value -- a 2D array of bytes 0..255, width*height in size -// -// pixel_dist_scale & onedge_value are a scale & bias that allows you to make -// optimal use of the limited 0..255 for your application, trading off precision -// and special effects. SDF values outside the range 0..255 are clamped to 0..255. -// -// Example: -// scale = stbtt_ScaleForPixelHeight(22) -// padding = 5 -// onedge_value = 180 -// pixel_dist_scale = 180/5.0 = 36.0 -// -// This will create an SDF bitmap in which the character is about 22 pixels -// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled -// shape, sample the SDF at each pixel and fill the pixel if the SDF value -// is greater than or equal to 180/255. (You'll actually want to antialias, -// which is beyond the scope of this example.) Additionally, you can compute -// offset outlines (e.g. to stroke the character border inside & outside, -// or only outside). For example, to fill outside the character up to 3 SDF -// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above -// choice of variables maps a range from 5 pixels outside the shape to -// 2 pixels inside the shape to 0..255; this is intended primarily for apply -// outside effects only (the interior range is needed to allow proper -// antialiasing of the font at *smaller* sizes) -// -// The function computes the SDF analytically at each SDF pixel, not by e.g. -// building a higher-res bitmap and approximating it. In theory the quality -// should be as high as possible for an SDF of this size & representation, but -// unclear if this is true in practice (perhaps building a higher-res bitmap -// and computing from that can allow drop-out prevention). -// -// The algorithm has not been optimized at all, so expect it to be slow -// if computing lots of characters or very large sizes. - - - -////////////////////////////////////////////////////////////////////////////// -// -// Finding the right font... -// -// You should really just solve this offline, keep your own tables -// of what font is what, and don't try to get it out of the .ttf file. -// That's because getting it out of the .ttf file is really hard, because -// the names in the file can appear in many possible encodings, in many -// possible languages, and e.g. if you need a case-insensitive comparison, -// the details of that depend on the encoding & language in a complex way -// (actually underspecified in truetype, but also gigantic). -// -// But you can use the provided functions in two possible ways: -// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on -// unicode-encoded names to try to find the font you want; -// you can run this before calling stbtt_InitFont() -// -// stbtt_GetFontNameString() lets you get any of the various strings -// from the file yourself and do your own comparisons on them. -// You have to have called stbtt_InitFont() first. - - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); -// returns the offset (not index) of the font that matches, or -1 if none -// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". -// if you use any other flag, use a font name like "Arial"; this checks -// the 'macStyle' header field; i don't know if fonts set this consistently -#define STBTT_MACSTYLE_DONTCARE 0 -#define STBTT_MACSTYLE_BOLD 1 -#define STBTT_MACSTYLE_ITALIC 2 -#define STBTT_MACSTYLE_UNDERSCORE 4 -#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); -// returns 1/0 whether the first string interpreted as utf8 is identical to -// the second string interpreted as big-endian utf16... useful for strings from next func - -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); -// returns the string (which may be big-endian double byte, e.g. for unicode) -// and puts the length in bytes in *length. -// -// some of the values for the IDs are below; for more see the truetype spec: -// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html -// http://www.microsoft.com/typography/otspec/name.htm - -enum { // platformID - STBTT_PLATFORM_ID_UNICODE =0, - STBTT_PLATFORM_ID_MAC =1, - STBTT_PLATFORM_ID_ISO =2, - STBTT_PLATFORM_ID_MICROSOFT =3 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_UNICODE - STBTT_UNICODE_EID_UNICODE_1_0 =0, - STBTT_UNICODE_EID_UNICODE_1_1 =1, - STBTT_UNICODE_EID_ISO_10646 =2, - STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, - STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT - STBTT_MS_EID_SYMBOL =0, - STBTT_MS_EID_UNICODE_BMP =1, - STBTT_MS_EID_SHIFTJIS =2, - STBTT_MS_EID_UNICODE_FULL =10 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes - STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, - STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, - STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, - STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 -}; - -enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... - // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs - STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, - STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, - STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, - STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, - STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, - STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D -}; - -enum { // languageID for STBTT_PLATFORM_ID_MAC - STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, - STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, - STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, - STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , - STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , - STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, - STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 -}; - -#ifdef __cplusplus -} -#endif - -#endif // __STB_INCLUDE_STB_TRUETYPE_H__ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// IMPLEMENTATION -//// -//// - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -#ifndef STBTT_MAX_OVERSAMPLE -#define STBTT_MAX_OVERSAMPLE 8 -#endif - -#if STBTT_MAX_OVERSAMPLE > 255 -#error "STBTT_MAX_OVERSAMPLE cannot be > 255" -#endif - -typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; - -#ifndef STBTT_RASTERIZER_VERSION -#define STBTT_RASTERIZER_VERSION 2 -#endif - -#ifdef _MSC_VER -#define STBTT__NOTUSED(v) (void)(v) -#else -#define STBTT__NOTUSED(v) (void)sizeof(v) -#endif - -////////////////////////////////////////////////////////////////////////// -// -// stbtt__buf helpers to parse data from file -// - -static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor++]; -} - -static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor]; -} - -static void stbtt__buf_seek(stbtt__buf *b, int o) -{ - STBTT_assert(!(o > b->size || o < 0)); - b->cursor = (o > b->size || o < 0) ? b->size : o; -} - -static void stbtt__buf_skip(stbtt__buf *b, int o) -{ - stbtt__buf_seek(b, b->cursor + o); -} - -static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) -{ - stbtt_uint32 v = 0; - int i; - STBTT_assert(n >= 1 && n <= 4); - for (i = 0; i < n; i++) - v = (v << 8) | stbtt__buf_get8(b); - return v; -} - -static stbtt__buf stbtt__new_buf(const void *p, size_t size) -{ - stbtt__buf r; - STBTT_assert(size < 0x40000000); - r.data = (stbtt_uint8*) p; - r.size = (int) size; - r.cursor = 0; - return r; -} - -#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) -#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) - -static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) -{ - stbtt__buf r = stbtt__new_buf(NULL, 0); - if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; - r.data = b->data + o; - r.size = s; - return r; -} - -static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) -{ - int count, start, offsize; - start = b->cursor; - count = stbtt__buf_get16(b); - if (count) { - offsize = stbtt__buf_get8(b); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(b, offsize * count); - stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); - } - return stbtt__buf_range(b, start, b->cursor - start); -} - -static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) -{ - int b0 = stbtt__buf_get8(b); - if (b0 >= 32 && b0 <= 246) return b0 - 139; - else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; - else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; - else if (b0 == 28) return stbtt__buf_get16(b); - else if (b0 == 29) return stbtt__buf_get32(b); - STBTT_assert(0); - return 0; -} - -static void stbtt__cff_skip_operand(stbtt__buf *b) { - int v, b0 = stbtt__buf_peek8(b); - STBTT_assert(b0 >= 28); - if (b0 == 30) { - stbtt__buf_skip(b, 1); - while (b->cursor < b->size) { - v = stbtt__buf_get8(b); - if ((v & 0xF) == 0xF || (v >> 4) == 0xF) - break; - } - } else { - stbtt__cff_int(b); - } -} - -static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) -{ - stbtt__buf_seek(b, 0); - while (b->cursor < b->size) { - int start = b->cursor, end, op; - while (stbtt__buf_peek8(b) >= 28) - stbtt__cff_skip_operand(b); - end = b->cursor; - op = stbtt__buf_get8(b); - if (op == 12) op = stbtt__buf_get8(b) | 0x100; - if (op == key) return stbtt__buf_range(b, start, end-start); - } - return stbtt__buf_range(b, 0, 0); -} - -static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) -{ - int i; - stbtt__buf operands = stbtt__dict_get(b, key); - for (i = 0; i < outcount && operands.cursor < operands.size; i++) - out[i] = stbtt__cff_int(&operands); -} - -static int stbtt__cff_index_count(stbtt__buf *b) -{ - stbtt__buf_seek(b, 0); - return stbtt__buf_get16(b); -} - -static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) -{ - int count, offsize, start, end; - stbtt__buf_seek(&b, 0); - count = stbtt__buf_get16(&b); - offsize = stbtt__buf_get8(&b); - STBTT_assert(i >= 0 && i < count); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(&b, i*offsize); - start = stbtt__buf_get(&b, offsize); - end = stbtt__buf_get(&b, offsize); - return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); -} - -////////////////////////////////////////////////////////////////////////// -// -// accessors to parse data from file -// - -// on platforms that don't allow misaligned reads, if we want to allow -// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE - -#define ttBYTE(p) (* (stbtt_uint8 *) (p)) -#define ttCHAR(p) (* (stbtt_int8 *) (p)) -#define ttFixed(p) ttLONG(p) - -static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } -static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } - -#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) -#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) - -static int stbtt__isfont(stbtt_uint8 *font) -{ - // check the version number - if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 - if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! - if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF - if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 - if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts - return 0; -} - -// @OPTIMIZE: binary search -static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) -{ - stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); - stbtt_uint32 tabledir = fontstart + 12; - stbtt_int32 i; - for (i=0; i < num_tables; ++i) { - stbtt_uint32 loc = tabledir + 16*i; - if (stbtt_tag(data+loc+0, tag)) - return ttULONG(data+loc+8); - } - return 0; -} - -static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) -{ - // if it's just a font, there's only one valid index - if (stbtt__isfont(font_collection)) - return index == 0 ? 0 : -1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - stbtt_int32 n = ttLONG(font_collection+8); - if (index >= n) - return -1; - return ttULONG(font_collection+12+index*4); - } - } - return -1; -} - -static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) -{ - // if it's just a font, there's only one valid font - if (stbtt__isfont(font_collection)) - return 1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - return ttLONG(font_collection+8); - } - } - return 0; -} - -static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) -{ - stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; - stbtt__buf pdict; - stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); - if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); - pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); - stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); - if (!subrsoff) return stbtt__new_buf(NULL, 0); - stbtt__buf_seek(&cff, private_loc[1]+subrsoff); - return stbtt__cff_get_index(&cff); -} - -// since most people won't use this, find this table the first time it's needed -static int stbtt__get_svg(stbtt_fontinfo *info) -{ - stbtt_uint32 t; - if (info->svg < 0) { - t = stbtt__find_table(info->data, info->fontstart, "SVG "); - if (t) { - stbtt_uint32 offset = ttULONG(info->data + t + 2); - info->svg = t + offset; - } else { - info->svg = 0; - } - } - return info->svg; -} - -static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) -{ - stbtt_uint32 cmap, t; - stbtt_int32 i,numTables; - - info->data = data; - info->fontstart = fontstart; - info->cff = stbtt__new_buf(NULL, 0); - - cmap = stbtt__find_table(data, fontstart, "cmap"); // required - info->loca = stbtt__find_table(data, fontstart, "loca"); // required - info->head = stbtt__find_table(data, fontstart, "head"); // required - info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required - info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required - info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required - info->kern = stbtt__find_table(data, fontstart, "kern"); // not required - info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required - - if (!cmap || !info->head || !info->hhea || !info->hmtx) - return 0; - if (info->glyf) { - // required for truetype - if (!info->loca) return 0; - } else { - // initialization for CFF / Type2 fonts (OTF) - stbtt__buf b, topdict, topdictidx; - stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; - stbtt_uint32 cff; - - cff = stbtt__find_table(data, fontstart, "CFF "); - if (!cff) return 0; - - info->fontdicts = stbtt__new_buf(NULL, 0); - info->fdselect = stbtt__new_buf(NULL, 0); - - // @TODO this should use size from table (not 512MB) - info->cff = stbtt__new_buf(data+cff, 512*1024*1024); - b = info->cff; - - // read the header - stbtt__buf_skip(&b, 2); - stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize - - // @TODO the name INDEX could list multiple fonts, - // but we just use the first one. - stbtt__cff_get_index(&b); // name INDEX - topdictidx = stbtt__cff_get_index(&b); - topdict = stbtt__cff_index_get(topdictidx, 0); - stbtt__cff_get_index(&b); // string INDEX - info->gsubrs = stbtt__cff_get_index(&b); - - stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); - stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); - stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); - stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); - info->subrs = stbtt__get_subrs(b, topdict); - - // we only support Type 2 charstrings - if (cstype != 2) return 0; - if (charstrings == 0) return 0; - - if (fdarrayoff) { - // looks like a CID font - if (!fdselectoff) return 0; - stbtt__buf_seek(&b, fdarrayoff); - info->fontdicts = stbtt__cff_get_index(&b); - info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); - } - - stbtt__buf_seek(&b, charstrings); - info->charstrings = stbtt__cff_get_index(&b); - } - - t = stbtt__find_table(data, fontstart, "maxp"); - if (t) - info->numGlyphs = ttUSHORT(data+t+4); - else - info->numGlyphs = 0xffff; - - info->svg = -1; - - // find a cmap encoding table we understand *now* to avoid searching - // later. (todo: could make this installable) - // the same regardless of glyph. - numTables = ttUSHORT(data + cmap + 2); - info->index_map = 0; - for (i=0; i < numTables; ++i) { - stbtt_uint32 encoding_record = cmap + 4 + 8 * i; - // find an encoding we understand: - switch(ttUSHORT(data+encoding_record)) { - case STBTT_PLATFORM_ID_MICROSOFT: - switch (ttUSHORT(data+encoding_record+2)) { - case STBTT_MS_EID_UNICODE_BMP: - case STBTT_MS_EID_UNICODE_FULL: - // MS/Unicode - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - break; - case STBTT_PLATFORM_ID_UNICODE: - // Mac/iOS has these - // all the encodingIDs are unicode, so we don't bother to check it - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - } - if (info->index_map == 0) - return 0; - - info->indexToLocFormat = ttUSHORT(data+info->head + 50); - return 1; -} - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) -{ - stbtt_uint8 *data = info->data; - stbtt_uint32 index_map = info->index_map; - - stbtt_uint16 format = ttUSHORT(data + index_map + 0); - if (format == 0) { // apple byte encoding - stbtt_int32 bytes = ttUSHORT(data + index_map + 2); - if (unicode_codepoint < bytes-6) - return ttBYTE(data + index_map + 6 + unicode_codepoint); - return 0; - } else if (format == 6) { - stbtt_uint32 first = ttUSHORT(data + index_map + 6); - stbtt_uint32 count = ttUSHORT(data + index_map + 8); - if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) - return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); - return 0; - } else if (format == 2) { - STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean - return 0; - } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges - stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; - stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; - stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); - stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; - - // do a binary search of the segments - stbtt_uint32 endCount = index_map + 14; - stbtt_uint32 search = endCount; - - if (unicode_codepoint > 0xffff) - return 0; - - // they lie from endCount .. endCount + segCount - // but searchRange is the nearest power of two, so... - if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) - search += rangeShift*2; - - // now decrement to bias correctly to find smallest - search -= 2; - while (entrySelector) { - stbtt_uint16 end; - searchRange >>= 1; - end = ttUSHORT(data + search + searchRange*2); - if (unicode_codepoint > end) - search += searchRange*2; - --entrySelector; - } - search += 2; - - { - stbtt_uint16 offset, start, last; - stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); - - start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); - last = ttUSHORT(data + endCount + 2*item); - if (unicode_codepoint < start || unicode_codepoint > last) - return 0; - - offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); - if (offset == 0) - return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); - - return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); - } - } else if (format == 12 || format == 13) { - stbtt_uint32 ngroups = ttULONG(data+index_map+12); - stbtt_int32 low,high; - low = 0; high = (stbtt_int32)ngroups; - // Binary search the right group. - while (low < high) { - stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high - stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); - stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); - if ((stbtt_uint32) unicode_codepoint < start_char) - high = mid; - else if ((stbtt_uint32) unicode_codepoint > end_char) - low = mid+1; - else { - stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); - if (format == 12) - return start_glyph + unicode_codepoint-start_char; - else // format == 13 - return start_glyph; - } - } - return 0; // not found - } - // @TODO - STBTT_assert(0); - return 0; -} - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) -{ - return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); -} - -static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) -{ - v->type = type; - v->x = (stbtt_int16) x; - v->y = (stbtt_int16) y; - v->cx = (stbtt_int16) cx; - v->cy = (stbtt_int16) cy; -} - -static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) -{ - int g1,g2; - - STBTT_assert(!info->cff.size); - - if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range - if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format - - if (info->indexToLocFormat == 0) { - g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; - g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; - } else { - g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); - g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); - } - - return g1==g2 ? -1 : g1; // if length is 0, return -1 -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); - -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - if (info->cff.size) { - stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); - } else { - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 0; - - if (x0) *x0 = ttSHORT(info->data + g + 2); - if (y0) *y0 = ttSHORT(info->data + g + 4); - if (x1) *x1 = ttSHORT(info->data + g + 6); - if (y1) *y1 = ttSHORT(info->data + g + 8); - } - return 1; -} - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) -{ - return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); -} - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt_int16 numberOfContours; - int g; - if (info->cff.size) - return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; - g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 1; - numberOfContours = ttSHORT(info->data + g); - return numberOfContours == 0; -} - -static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, - stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) -{ - if (start_off) { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); - } - return num_vertices; -} - -static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - stbtt_int16 numberOfContours; - stbtt_uint8 *endPtsOfContours; - stbtt_uint8 *data = info->data; - stbtt_vertex *vertices=0; - int num_vertices=0; - int g = stbtt__GetGlyfOffset(info, glyph_index); - - *pvertices = NULL; - - if (g < 0) return 0; - - numberOfContours = ttSHORT(data + g); - - if (numberOfContours > 0) { - stbtt_uint8 flags=0,flagcount; - stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; - stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; - stbtt_uint8 *points; - endPtsOfContours = (data + g + 10); - ins = ttUSHORT(data + g + 10 + numberOfContours * 2); - points = data + g + 10 + numberOfContours * 2 + 2 + ins; - - n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); - - m = n + 2*numberOfContours; // a loose bound on how many vertices we might need - vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); - if (vertices == 0) - return 0; - - next_move = 0; - flagcount=0; - - // in first pass, we load uninterpreted data into the allocated array - // above, shifted to the end of the array so we won't overwrite it when - // we create our final data starting from the front - - off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated - - // first load flags - - for (i=0; i < n; ++i) { - if (flagcount == 0) { - flags = *points++; - if (flags & 8) - flagcount = *points++; - } else - --flagcount; - vertices[off+i].type = flags; - } - - // now load x coordinates - x=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 2) { - stbtt_int16 dx = *points++; - x += (flags & 16) ? dx : -dx; // ??? - } else { - if (!(flags & 16)) { - x = x + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].x = (stbtt_int16) x; - } - - // now load y coordinates - y=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 4) { - stbtt_int16 dy = *points++; - y += (flags & 32) ? dy : -dy; // ??? - } else { - if (!(flags & 32)) { - y = y + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].y = (stbtt_int16) y; - } - - // now convert them to our format - num_vertices=0; - sx = sy = cx = cy = scx = scy = 0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - x = (stbtt_int16) vertices[off+i].x; - y = (stbtt_int16) vertices[off+i].y; - - if (next_move == i) { - if (i != 0) - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - - // now start the new one - start_off = !(flags & 1); - if (start_off) { - // if we start off with an off-curve point, then when we need to find a point on the curve - // where we can start, and we need to save some state for when we wraparound. - scx = x; - scy = y; - if (!(vertices[off+i+1].type & 1)) { - // next point is also a curve point, so interpolate an on-point curve - sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; - sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; - } else { - // otherwise just use the next point as our start point - sx = (stbtt_int32) vertices[off+i+1].x; - sy = (stbtt_int32) vertices[off+i+1].y; - ++i; // we're using point i+1 as the starting point, so skip it - } - } else { - sx = x; - sy = y; - } - stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); - was_off = 0; - next_move = 1 + ttUSHORT(endPtsOfContours+j*2); - ++j; - } else { - if (!(flags & 1)) { // if it's a curve - if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); - cx = x; - cy = y; - was_off = 1; - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); - was_off = 0; - } - } - } - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - } else if (numberOfContours < 0) { - // Compound shapes. - int more = 1; - stbtt_uint8 *comp = data + g + 10; - num_vertices = 0; - vertices = 0; - while (more) { - stbtt_uint16 flags, gidx; - int comp_num_verts = 0, i; - stbtt_vertex *comp_verts = 0, *tmp = 0; - float mtx[6] = {1,0,0,1,0,0}, m, n; - - flags = ttSHORT(comp); comp+=2; - gidx = ttSHORT(comp); comp+=2; - - if (flags & 2) { // XY values - if (flags & 1) { // shorts - mtx[4] = ttSHORT(comp); comp+=2; - mtx[5] = ttSHORT(comp); comp+=2; - } else { - mtx[4] = ttCHAR(comp); comp+=1; - mtx[5] = ttCHAR(comp); comp+=1; - } - } - else { - // @TODO handle matching point - STBTT_assert(0); - } - if (flags & (1<<3)) { // WE_HAVE_A_SCALE - mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } - - // Find transformation scales. - m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); - n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); - - // Get indexed glyph. - comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); - if (comp_num_verts > 0) { - // Transform vertices. - for (i = 0; i < comp_num_verts; ++i) { - stbtt_vertex* v = &comp_verts[i]; - stbtt_vertex_type x,y; - x=v->x; y=v->y; - v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - x=v->cx; y=v->cy; - v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - } - // Append vertices. - tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); - if (!tmp) { - if (vertices) STBTT_free(vertices, info->userdata); - if (comp_verts) STBTT_free(comp_verts, info->userdata); - return 0; - } - if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); - STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); - if (vertices) STBTT_free(vertices, info->userdata); - vertices = tmp; - STBTT_free(comp_verts, info->userdata); - num_vertices += comp_num_verts; - } - // More components ? - more = flags & (1<<5); - } - } else { - // numberOfCounters == 0, do nothing - } - - *pvertices = vertices; - return num_vertices; -} - -typedef struct -{ - int bounds; - int started; - float first_x, first_y; - float x, y; - stbtt_int32 min_x, max_x, min_y, max_y; - - stbtt_vertex *pvertices; - int num_vertices; -} stbtt__csctx; - -#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} - -static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) -{ - if (x > c->max_x || !c->started) c->max_x = x; - if (y > c->max_y || !c->started) c->max_y = y; - if (x < c->min_x || !c->started) c->min_x = x; - if (y < c->min_y || !c->started) c->min_y = y; - c->started = 1; -} - -static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) -{ - if (c->bounds) { - stbtt__track_vertex(c, x, y); - if (type == STBTT_vcubic) { - stbtt__track_vertex(c, cx, cy); - stbtt__track_vertex(c, cx1, cy1); - } - } else { - stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); - c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; - c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; - } - c->num_vertices++; -} - -static void stbtt__csctx_close_shape(stbtt__csctx *ctx) -{ - if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) -{ - stbtt__csctx_close_shape(ctx); - ctx->first_x = ctx->x = ctx->x + dx; - ctx->first_y = ctx->y = ctx->y + dy; - stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) -{ - ctx->x += dx; - ctx->y += dy; - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) -{ - float cx1 = ctx->x + dx1; - float cy1 = ctx->y + dy1; - float cx2 = cx1 + dx2; - float cy2 = cy1 + dy2; - ctx->x = cx2 + dx3; - ctx->y = cy2 + dy3; - stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); -} - -static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) -{ - int count = stbtt__cff_index_count(&idx); - int bias = 107; - if (count >= 33900) - bias = 32768; - else if (count >= 1240) - bias = 1131; - n += bias; - if (n < 0 || n >= count) - return stbtt__new_buf(NULL, 0); - return stbtt__cff_index_get(idx, n); -} - -static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt__buf fdselect = info->fdselect; - int nranges, start, end, v, fmt, fdselector = -1, i; - - stbtt__buf_seek(&fdselect, 0); - fmt = stbtt__buf_get8(&fdselect); - if (fmt == 0) { - // untested - stbtt__buf_skip(&fdselect, glyph_index); - fdselector = stbtt__buf_get8(&fdselect); - } else if (fmt == 3) { - nranges = stbtt__buf_get16(&fdselect); - start = stbtt__buf_get16(&fdselect); - for (i = 0; i < nranges; i++) { - v = stbtt__buf_get8(&fdselect); - end = stbtt__buf_get16(&fdselect); - if (glyph_index >= start && glyph_index < end) { - fdselector = v; - break; - } - start = end; - } - } - if (fdselector == -1) stbtt__new_buf(NULL, 0); - return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); -} - -static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) -{ - int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; - int has_subrs = 0, clear_stack; - float s[48]; - stbtt__buf subr_stack[10], subrs = info->subrs, b; - float f; - -#define STBTT__CSERR(s) (0) - - // this currently ignores the initial width value, which isn't needed if we have hmtx - b = stbtt__cff_index_get(info->charstrings, glyph_index); - while (b.cursor < b.size) { - i = 0; - clear_stack = 1; - b0 = stbtt__buf_get8(&b); - switch (b0) { - // @TODO implement hinting - case 0x13: // hintmask - case 0x14: // cntrmask - if (in_header) - maskbits += (sp / 2); // implicit "vstem" - in_header = 0; - stbtt__buf_skip(&b, (maskbits + 7) / 8); - break; - - case 0x01: // hstem - case 0x03: // vstem - case 0x12: // hstemhm - case 0x17: // vstemhm - maskbits += (sp / 2); - break; - - case 0x15: // rmoveto - in_header = 0; - if (sp < 2) return STBTT__CSERR("rmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); - break; - case 0x04: // vmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("vmoveto stack"); - stbtt__csctx_rmove_to(c, 0, s[sp-1]); - break; - case 0x16: // hmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("hmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-1], 0); - break; - - case 0x05: // rlineto - if (sp < 2) return STBTT__CSERR("rlineto stack"); - for (; i + 1 < sp; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical - // starting from a different place. - - case 0x07: // vlineto - if (sp < 1) return STBTT__CSERR("vlineto stack"); - goto vlineto; - case 0x06: // hlineto - if (sp < 1) return STBTT__CSERR("hlineto stack"); - for (;;) { - if (i >= sp) break; - stbtt__csctx_rline_to(c, s[i], 0); - i++; - vlineto: - if (i >= sp) break; - stbtt__csctx_rline_to(c, 0, s[i]); - i++; - } - break; - - case 0x1F: // hvcurveto - if (sp < 4) return STBTT__CSERR("hvcurveto stack"); - goto hvcurveto; - case 0x1E: // vhcurveto - if (sp < 4) return STBTT__CSERR("vhcurveto stack"); - for (;;) { - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); - i += 4; - hvcurveto: - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); - i += 4; - } - break; - - case 0x08: // rrcurveto - if (sp < 6) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x18: // rcurveline - if (sp < 8) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp - 2; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - case 0x19: // rlinecurve - if (sp < 8) return STBTT__CSERR("rlinecurve stack"); - for (; i + 1 < sp - 6; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x1A: // vvcurveto - case 0x1B: // hhcurveto - if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); - f = 0.0; - if (sp & 1) { f = s[i]; i++; } - for (; i + 3 < sp; i += 4) { - if (b0 == 0x1B) - stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); - else - stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); - f = 0.0; - } - break; - - case 0x0A: // callsubr - if (!has_subrs) { - if (info->fdselect.size) - subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); - has_subrs = 1; - } - // FALLTHROUGH - case 0x1D: // callgsubr - if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); - v = (int) s[--sp]; - if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); - subr_stack[subr_stack_height++] = b; - b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); - if (b.size == 0) return STBTT__CSERR("subr not found"); - b.cursor = 0; - clear_stack = 0; - break; - - case 0x0B: // return - if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); - b = subr_stack[--subr_stack_height]; - clear_stack = 0; - break; - - case 0x0E: // endchar - stbtt__csctx_close_shape(c); - return 1; - - case 0x0C: { // two-byte escape - float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; - float dx, dy; - int b1 = stbtt__buf_get8(&b); - switch (b1) { - // @TODO These "flex" implementations ignore the flex-depth and resolution, - // and always draw beziers. - case 0x22: // hflex - if (sp < 7) return STBTT__CSERR("hflex stack"); - dx1 = s[0]; - dx2 = s[1]; - dy2 = s[2]; - dx3 = s[3]; - dx4 = s[4]; - dx5 = s[5]; - dx6 = s[6]; - stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); - break; - - case 0x23: // flex - if (sp < 13) return STBTT__CSERR("flex stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = s[10]; - dy6 = s[11]; - //fd is s[12] - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - case 0x24: // hflex1 - if (sp < 9) return STBTT__CSERR("hflex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dx4 = s[5]; - dx5 = s[6]; - dy5 = s[7]; - dx6 = s[8]; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); - break; - - case 0x25: // flex1 - if (sp < 11) return STBTT__CSERR("flex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = dy6 = s[10]; - dx = dx1+dx2+dx3+dx4+dx5; - dy = dy1+dy2+dy3+dy4+dy5; - if (STBTT_fabs(dx) > STBTT_fabs(dy)) - dy6 = -dy; - else - dx6 = -dx; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - default: - return STBTT__CSERR("unimplemented"); - } - } break; - - default: - if (b0 != 255 && b0 != 28 && b0 < 32) - return STBTT__CSERR("reserved operator"); - - // push immediate - if (b0 == 255) { - f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; - } else { - stbtt__buf_skip(&b, -1); - f = (float)(stbtt_int16)stbtt__cff_int(&b); - } - if (sp >= 48) return STBTT__CSERR("push stack overflow"); - s[sp++] = f; - clear_stack = 0; - break; - } - if (clear_stack) sp = 0; - } - return STBTT__CSERR("no endchar"); - -#undef STBTT__CSERR -} - -static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - // runs the charstring twice, once to count and once to output (to avoid realloc) - stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); - stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); - if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { - *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); - output_ctx.pvertices = *pvertices; - if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { - STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); - return output_ctx.num_vertices; - } - } - *pvertices = NULL; - return 0; -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - stbtt__csctx c = STBTT__CSCTX_INIT(1); - int r = stbtt__run_charstring(info, glyph_index, &c); - if (x0) *x0 = r ? c.min_x : 0; - if (y0) *y0 = r ? c.min_y : 0; - if (x1) *x1 = r ? c.max_x : 0; - if (y1) *y1 = r ? c.max_y : 0; - return r ? c.num_vertices : 0; -} - -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - if (!info->cff.size) - return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); - else - return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); -} - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) -{ - stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); - if (glyph_index < numOfLongHorMetrics) { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); - } else { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); - } -} - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) -{ - stbtt_uint8 *data = info->data + info->kern; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - return ttUSHORT(data+10); -} - -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) -{ - stbtt_uint8 *data = info->data + info->kern; - int k, length; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - length = ttUSHORT(data+10); - if (table_length < length) - length = table_length; - - for (k = 0; k < length; k++) - { - table[k].glyph1 = ttUSHORT(data+18+(k*6)); - table[k].glyph2 = ttUSHORT(data+20+(k*6)); - table[k].advance = ttSHORT(data+22+(k*6)); - } - - return length; -} - -static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint8 *data = info->data + info->kern; - stbtt_uint32 needle, straw; - int l, r, m; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - l = 0; - r = ttUSHORT(data+10) - 1; - needle = glyph1 << 16 | glyph2; - while (l <= r) { - m = (l + r) >> 1; - straw = ttULONG(data+18+(m*6)); // note: unaligned read - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else - return ttSHORT(data+22+(m*6)); - } - return 0; -} - -static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) -{ - stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); - switch (coverageFormat) { - case 1: { - stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); - - // Binary search. - stbtt_int32 l=0, r=glyphCount-1, m; - int straw, needle=glyph; - while (l <= r) { - stbtt_uint8 *glyphArray = coverageTable + 4; - stbtt_uint16 glyphID; - m = (l + r) >> 1; - glyphID = ttUSHORT(glyphArray + 2 * m); - straw = glyphID; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - return m; - } - } - break; - } - - case 2: { - stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); - stbtt_uint8 *rangeArray = coverageTable + 4; - - // Binary search. - stbtt_int32 l=0, r=rangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *rangeRecord; - m = (l + r) >> 1; - rangeRecord = rangeArray + 6 * m; - strawStart = ttUSHORT(rangeRecord); - strawEnd = ttUSHORT(rangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else { - stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); - return startCoverageIndex + glyph - strawStart; - } - } - break; - } - - default: return -1; // unsupported - } - - return -1; -} - -static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) -{ - stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); - switch (classDefFormat) - { - case 1: { - stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); - stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); - stbtt_uint8 *classDef1ValueArray = classDefTable + 6; - - if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) - return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); - break; - } - - case 2: { - stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); - stbtt_uint8 *classRangeRecords = classDefTable + 4; - - // Binary search. - stbtt_int32 l=0, r=classRangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *classRangeRecord; - m = (l + r) >> 1; - classRangeRecord = classRangeRecords + 6 * m; - strawStart = ttUSHORT(classRangeRecord); - strawEnd = ttUSHORT(classRangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else - return (stbtt_int32)ttUSHORT(classRangeRecord + 4); - } - break; - } - - default: - return -1; // Unsupported definition type, return an error. - } - - // "All glyphs not assigned to a class fall into class 0". (OpenType spec) - return 0; -} - -// Define to STBTT_assert(x) if you want to break on unimplemented formats. -#define STBTT_GPOS_TODO_assert(x) - -static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint16 lookupListOffset; - stbtt_uint8 *lookupList; - stbtt_uint16 lookupCount; - stbtt_uint8 *data; - stbtt_int32 i, sti; - - if (!info->gpos) return 0; - - data = info->data + info->gpos; - - if (ttUSHORT(data+0) != 1) return 0; // Major version 1 - if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 - - lookupListOffset = ttUSHORT(data+8); - lookupList = data + lookupListOffset; - lookupCount = ttUSHORT(lookupList); - - for (i=0; i= pairSetCount) return 0; - - needle=glyph2; - r=pairValueCount-1; - l=0; - - // Binary search. - while (l <= r) { - stbtt_uint16 secondGlyph; - stbtt_uint8 *pairValue; - m = (l + r) >> 1; - pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; - secondGlyph = ttUSHORT(pairValue); - straw = secondGlyph; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - stbtt_int16 xAdvance = ttSHORT(pairValue + 2); - return xAdvance; - } - } - } else - return 0; - break; - } - - case 2: { - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? - stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); - stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); - int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); - int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); - - stbtt_uint16 class1Count = ttUSHORT(table + 12); - stbtt_uint16 class2Count = ttUSHORT(table + 14); - stbtt_uint8 *class1Records, *class2Records; - stbtt_int16 xAdvance; - - if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed - if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed - - class1Records = table + 16; - class2Records = class1Records + 2 * (glyph1class * class2Count); - xAdvance = ttSHORT(class2Records + 2 * glyph2class); - return xAdvance; - } else - return 0; - break; - } - - default: - return 0; // Unsupported position format - } - } - } - - return 0; -} - -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) -{ - int xAdvance = 0; - - if (info->gpos) - xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); - else if (info->kern) - xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); - - return xAdvance; -} - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) -{ - if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs - return 0; - return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); -} - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) -{ - stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); -} - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) -{ - if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); - if (descent) *descent = ttSHORT(info->data+info->hhea + 6); - if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); -} - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) -{ - int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); - if (!tab) - return 0; - if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); - if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); - if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); - return 1; -} - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) -{ - *x0 = ttSHORT(info->data + info->head + 36); - *y0 = ttSHORT(info->data + info->head + 38); - *x1 = ttSHORT(info->data + info->head + 40); - *y1 = ttSHORT(info->data + info->head + 42); -} - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) -{ - int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); - return (float) height / fheight; -} - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) -{ - int unitsPerEm = ttUSHORT(info->data + info->head + 18); - return pixels / unitsPerEm; -} - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) -{ - STBTT_free(v, info->userdata); -} - -STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) -{ - int i; - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); - - int numEntries = ttUSHORT(svg_doc_list); - stbtt_uint8 *svg_docs = svg_doc_list + 2; - - for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) - return svg_doc; - } - return 0; -} - -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) -{ - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc; - - if (info->svg == 0) - return 0; - - svg_doc = stbtt_FindSVGDoc(info, gl); - if (svg_doc != NULL) { - *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); - return ttULONG(svg_doc + 8); - } else { - return 0; - } -} - -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) -{ - return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); -} - -////////////////////////////////////////////////////////////////////////////// -// -// antialiasing software rasterizer -// - -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning - if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { - // e.g. space character - if (ix0) *ix0 = 0; - if (iy0) *iy0 = 0; - if (ix1) *ix1 = 0; - if (iy1) *iy1 = 0; - } else { - // move to integral bboxes (treating pixels as little squares, what pixels get touched)? - if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); - if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); - if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); - if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); - } -} - -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); -} - -////////////////////////////////////////////////////////////////////////////// -// -// Rasterizer - -typedef struct stbtt__hheap_chunk -{ - struct stbtt__hheap_chunk *next; -} stbtt__hheap_chunk; - -typedef struct stbtt__hheap -{ - struct stbtt__hheap_chunk *head; - void *first_free; - int num_remaining_in_head_chunk; -} stbtt__hheap; - -static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) -{ - if (hh->first_free) { - void *p = hh->first_free; - hh->first_free = * (void **) p; - return p; - } else { - if (hh->num_remaining_in_head_chunk == 0) { - int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); - stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); - if (c == NULL) - return NULL; - c->next = hh->head; - hh->head = c; - hh->num_remaining_in_head_chunk = count; - } - --hh->num_remaining_in_head_chunk; - return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; - } -} - -static void stbtt__hheap_free(stbtt__hheap *hh, void *p) -{ - *(void **) p = hh->first_free; - hh->first_free = p; -} - -static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) -{ - stbtt__hheap_chunk *c = hh->head; - while (c) { - stbtt__hheap_chunk *n = c->next; - STBTT_free(c, userdata); - c = n; - } -} - -typedef struct stbtt__edge { - float x0,y0, x1,y1; - int invert; -} stbtt__edge; - - -typedef struct stbtt__active_edge -{ - struct stbtt__active_edge *next; - #if STBTT_RASTERIZER_VERSION==1 - int x,dx; - float ey; - int direction; - #elif STBTT_RASTERIZER_VERSION==2 - float fx,fdx,fdy; - float direction; - float sy; - float ey; - #else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" - #endif -} stbtt__active_edge; - -#if STBTT_RASTERIZER_VERSION == 1 -#define STBTT_FIXSHIFT 10 -#define STBTT_FIX (1 << STBTT_FIXSHIFT) -#define STBTT_FIXMASK (STBTT_FIX-1) - -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - if (!z) return z; - - // round dx down to avoid overshooting - if (dxdy < 0) - z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); - else - z->dx = STBTT_ifloor(STBTT_FIX * dxdy); - - z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount - z->x -= off_x * STBTT_FIX; - - z->ey = e->y1; - z->next = 0; - z->direction = e->invert ? 1 : -1; - return z; -} -#elif STBTT_RASTERIZER_VERSION == 2 -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - //STBTT_assert(e->y0 <= start_point); - if (!z) return z; - z->fdx = dxdy; - z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; - z->fx = e->x0 + dxdy * (start_point - e->y0); - z->fx -= off_x; - z->direction = e->invert ? 1.0f : -1.0f; - z->sy = e->y0; - z->ey = e->y1; - z->next = 0; - return z; -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#if STBTT_RASTERIZER_VERSION == 1 -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) -{ - // non-zero winding fill - int x0=0, w=0; - - while (e) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->direction; - } else { - int x1 = e->x; w += e->direction; - // if we went to zero, we need to draw - if (w == 0) { - int i = x0 >> STBTT_FIXSHIFT; - int j = x1 >> STBTT_FIXSHIFT; - - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = scanline[i] + (stbtt_uint8) max_weight; - } - } - } - } - - e = e->next; - } -} - -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0; - int max_weight = (255 / vsubsample); // weight per vertical scanline - int s; // vertical subsample index - unsigned char scanline_data[512], *scanline; - - if (result->w > 512) - scanline = (unsigned char *) STBTT_malloc(result->w, userdata); - else - scanline = scanline_data; - - y = off_y * vsubsample; - e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; - - while (j < result->h) { - STBTT_memset(scanline, 0, result->w); - for (s=0; s < vsubsample; ++s) { - // find center of pixel for this scanline - float scan_y = y + 0.5f; - stbtt__active_edge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for(;;) { - int changed=0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - stbtt__active_edge *t = *step; - stbtt__active_edge *q = t->next; - - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e->y0 <= scan_y) { - if (e->y1 > scan_y) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); - if (z != NULL) { - // find insertion point - if (active == NULL) - active = z; - else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - stbtt__active_edge *p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - } - ++e; - } - - // now process all active edges in XOR fashion - if (active) - stbtt__fill_active_edges(scanline, result->w, active, max_weight); - - ++y; - } - STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} - -#elif STBTT_RASTERIZER_VERSION == 2 - -// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 -// (i.e. it has already been clipped to those) -static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) -{ - if (y0 == y1) return; - STBTT_assert(y0 < y1); - STBTT_assert(e->sy <= e->ey); - if (y0 > e->ey) return; - if (y1 < e->sy) return; - if (y0 < e->sy) { - x0 += (x1-x0) * (e->sy - y0) / (y1-y0); - y0 = e->sy; - } - if (y1 > e->ey) { - x1 += (x1-x0) * (e->ey - y1) / (y1-y0); - y1 = e->ey; - } - - if (x0 == x) - STBTT_assert(x1 <= x+1); - else if (x0 == x+1) - STBTT_assert(x1 >= x); - else if (x0 <= x) - STBTT_assert(x1 <= x); - else if (x0 >= x+1) - STBTT_assert(x1 >= x+1); - else - STBTT_assert(x1 >= x && x1 <= x+1); - - if (x0 <= x && x1 <= x) - scanline[x] += e->direction * (y1-y0); - else if (x0 >= x+1 && x1 >= x+1) - ; - else { - STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); - scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position - } -} - -static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) -{ - STBTT_assert(top_width >= 0); - STBTT_assert(bottom_width >= 0); - return (top_width + bottom_width) / 2.0f * height; -} - -static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) -{ - return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); -} - -static float stbtt__sized_triangle_area(float height, float width) -{ - return height * width / 2; -} - -static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) -{ - float y_bottom = y_top+1; - - while (e) { - // brute force every pixel - - // compute intersection points with top & bottom - STBTT_assert(e->ey >= y_top); - - if (e->fdx == 0) { - float x0 = e->fx; - if (x0 < len) { - if (x0 >= 0) { - stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); - stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); - } else { - stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); - } - } - } else { - float x0 = e->fx; - float dx = e->fdx; - float xb = x0 + dx; - float x_top, x_bottom; - float sy0,sy1; - float dy = e->fdy; - STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); - - // compute endpoints of line segment clipped to this scanline (if the - // line segment starts on this scanline. x0 is the intersection of the - // line with y_top, but that may be off the line segment. - if (e->sy > y_top) { - x_top = x0 + dx * (e->sy - y_top); - sy0 = e->sy; - } else { - x_top = x0; - sy0 = y_top; - } - if (e->ey < y_bottom) { - x_bottom = x0 + dx * (e->ey - y_top); - sy1 = e->ey; - } else { - x_bottom = xb; - sy1 = y_bottom; - } - - if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { - // from here on, we don't have to range check x values - - if ((int) x_top == (int) x_bottom) { - float height; - // simple case, only spans one pixel - int x = (int) x_top; - height = (sy1 - sy0) * e->direction; - STBTT_assert(x >= 0 && x < len); - scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); - scanline_fill[x] += height; // everything right of this pixel is filled - } else { - int x,x1,x2; - float y_crossing, y_final, step, sign, area; - // covers 2+ pixels - if (x_top > x_bottom) { - // flip scanline vertically; signed area is the same - float t; - sy0 = y_bottom - (sy0 - y_top); - sy1 = y_bottom - (sy1 - y_top); - t = sy0, sy0 = sy1, sy1 = t; - t = x_bottom, x_bottom = x_top, x_top = t; - dx = -dx; - dy = -dy; - t = x0, x0 = xb, xb = t; - } - STBTT_assert(dy >= 0); - STBTT_assert(dx >= 0); - - x1 = (int) x_top; - x2 = (int) x_bottom; - // compute intersection with y axis at x1+1 - y_crossing = y_top + dy * (x1+1 - x0); - - // compute intersection with y axis at x2 - y_final = y_top + dy * (x2 - x0); - - // x1 x_top x2 x_bottom - // y_top +------|-----+------------+------------+--------|---+------------+ - // | | | | | | - // | | | | | | - // sy0 | Txxxxx|............|............|............|............| - // y_crossing | *xxxxx.......|............|............|............| - // | | xxxxx..|............|............|............| - // | | /- xx*xxxx........|............|............| - // | | dy < | xxxxxx..|............|............| - // y_final | | \- | xx*xxx.........|............| - // sy1 | | | | xxxxxB...|............| - // | | | | | | - // | | | | | | - // y_bottom +------------+------------+------------+------------+------------+ - // - // goal is to measure the area covered by '.' in each pixel - - // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 - // @TODO: maybe test against sy1 rather than y_bottom? - if (y_crossing > y_bottom) - y_crossing = y_bottom; - - sign = e->direction; - - // area of the rectangle covered from sy0..y_crossing - area = sign * (y_crossing-sy0); - - // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) - scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); - - // check if final y_crossing is blown up; no test case for this - if (y_final > y_bottom) { - y_final = y_bottom; - dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom - } - - // in second pixel, area covered by line segment found in first pixel - // is always a rectangle 1 wide * the height of that line segment; this - // is exactly what the variable 'area' stores. it also gets a contribution - // from the line segment within it. the THIRD pixel will get the first - // pixel's rectangle contribution, the second pixel's rectangle contribution, - // and its own contribution. the 'own contribution' is the same in every pixel except - // the leftmost and rightmost, a trapezoid that slides down in each pixel. - // the second pixel's contribution to the third pixel will be the - // rectangle 1 wide times the height change in the second pixel, which is dy. - - step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, - // which multiplied by 1-pixel-width is how much pixel area changes for each step in x - // so the area advances by 'step' every time - - for (x = x1+1; x < x2; ++x) { - scanline[x] += area + step/2; // area of trapezoid is 1*step/2 - area += step; - } - STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down - STBTT_assert(sy1 > y_final-0.01f); - - // area covered in the last pixel is the rectangle from all the pixels to the left, - // plus the trapezoid filled by the line segment in this pixel all the way to the right edge - scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); - - // the rest of the line is filled based on the total height of the line segment in this pixel - scanline_fill[x2] += sign * (sy1-sy0); - } - } else { - // if edge goes outside of box we're drawing, we require - // clipping logic. since this does not match the intended use - // of this library, we use a different, very slow brute - // force implementation - // note though that this does happen some of the time because - // x_top and x_bottom can be extrapolated at the top & bottom of - // the shape and actually lie outside the bounding box - int x; - for (x=0; x < len; ++x) { - // cases: - // - // there can be up to two intersections with the pixel. any intersection - // with left or right edges can be handled by splitting into two (or three) - // regions. intersections with top & bottom do not necessitate case-wise logic. - // - // the old way of doing this found the intersections with the left & right edges, - // then used some simple logic to produce up to three segments in sorted order - // from top-to-bottom. however, this had a problem: if an x edge was epsilon - // across the x border, then the corresponding y position might not be distinct - // from the other y segment, and it might ignored as an empty segment. to avoid - // that, we need to explicitly produce segments based on x positions. - - // rename variables to clearly-defined pairs - float y0 = y_top; - float x1 = (float) (x); - float x2 = (float) (x+1); - float x3 = xb; - float y3 = y_bottom; - - // x = e->x + e->dx * (y-y_top) - // (y-y_top) = (x - e->x) / e->dx - // y = (x - e->x) / e->dx + y_top - float y1 = (x - x0) / dx + y_top; - float y2 = (x+1 - x0) / dx + y_top; - - if (x0 < x1 && x3 > x2) { // three segments descending down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x1 && x0 > x2) { // three segments descending down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else { // one segment - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); - } - } - } - } - e = e->next; - } -} - -// directly AA rasterize edges w/o supersampling -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0, i; - float scanline_data[129], *scanline, *scanline2; - - STBTT__NOTUSED(vsubsample); - - if (result->w > 64) - scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); - else - scanline = scanline_data; - - scanline2 = scanline + result->w; - - y = off_y; - e[n].y0 = (float) (off_y + result->h) + 1; - - while (j < result->h) { - // find center of pixel for this scanline - float scan_y_top = y + 0.0f; - float scan_y_bottom = y + 1.0f; - stbtt__active_edge **step = &active; - - STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); - STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); - - // update all active edges; - // remove all active edges that terminate before the top of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y_top) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - step = &((*step)->next); // advance through list - } - } - - // insert all edges that start before the bottom of this scanline - while (e->y0 <= scan_y_bottom) { - if (e->y0 != e->y1) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); - if (z != NULL) { - if (j == 0 && off_y != 0) { - if (z->ey < scan_y_top) { - // this can happen due to subpixel positioning and some kind of fp rounding error i think - z->ey = scan_y_top; - } - } - STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds - // insert at front - z->next = active; - active = z; - } - } - ++e; - } - - // now process all active edges - if (active) - stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); - - { - float sum = 0; - for (i=0; i < result->w; ++i) { - float k; - int m; - sum += scanline2[i]; - k = scanline[i] + sum; - k = (float) STBTT_fabs(k)*255 + 0.5f; - m = (int) k; - if (m > 255) m = 255; - result->pixels[j*result->stride + i] = (unsigned char) m; - } - } - // advance all the edges - step = &active; - while (*step) { - stbtt__active_edge *z = *step; - z->fx += z->fdx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - - ++y; - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) - -static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) -{ - int i,j; - for (i=1; i < n; ++i) { - stbtt__edge t = p[i], *a = &t; - j = i; - while (j > 0) { - stbtt__edge *b = &p[j-1]; - int c = STBTT__COMPARE(a,b); - if (!c) break; - p[j] = p[j-1]; - --j; - } - if (i != j) - p[j] = t; - } -} - -static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) -{ - /* threshold for transitioning to insertion sort */ - while (n > 12) { - stbtt__edge t; - int c01,c12,c,m,i,j; - - /* compute median of three */ - m = n >> 1; - c01 = STBTT__COMPARE(&p[0],&p[m]); - c12 = STBTT__COMPARE(&p[m],&p[n-1]); - /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ - if (c01 != c12) { - /* otherwise, we'll need to swap something else to middle */ - int z; - c = STBTT__COMPARE(&p[0],&p[n-1]); - /* 0>mid && midn => n; 0 0 */ - /* 0n: 0>n => 0; 0 n */ - z = (c == c12) ? 0 : n-1; - t = p[z]; - p[z] = p[m]; - p[m] = t; - } - /* now p[m] is the median-of-three */ - /* swap it to the beginning so it won't move around */ - t = p[0]; - p[0] = p[m]; - p[m] = t; - - /* partition loop */ - i=1; - j=n-1; - for(;;) { - /* handling of equality is crucial here */ - /* for sentinels & efficiency with duplicates */ - for (;;++i) { - if (!STBTT__COMPARE(&p[i], &p[0])) break; - } - for (;;--j) { - if (!STBTT__COMPARE(&p[0], &p[j])) break; - } - /* make sure we haven't crossed */ - if (i >= j) break; - t = p[i]; - p[i] = p[j]; - p[j] = t; - - ++i; - --j; - } - /* recurse on smaller side, iterate on larger */ - if (j < (n-i)) { - stbtt__sort_edges_quicksort(p,j); - p = p+i; - n = n-i; - } else { - stbtt__sort_edges_quicksort(p+i, n-i); - n = j; - } - } -} - -static void stbtt__sort_edges(stbtt__edge *p, int n) -{ - stbtt__sort_edges_quicksort(p, n); - stbtt__sort_edges_ins_sort(p, n); -} - -typedef struct -{ - float x,y; -} stbtt__point; - -static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) -{ - float y_scale_inv = invert ? -scale_y : scale_y; - stbtt__edge *e; - int n,i,j,k,m; -#if STBTT_RASTERIZER_VERSION == 1 - int vsubsample = result->h < 8 ? 15 : 5; -#elif STBTT_RASTERIZER_VERSION == 2 - int vsubsample = 1; -#else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - // vsubsample should divide 255 evenly; otherwise we won't reach full opacity - - // now we have to blow out the windings into explicit edge lists - n = 0; - for (i=0; i < windings; ++i) - n += wcount[i]; - - e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel - if (e == 0) return; - n = 0; - - m=0; - for (i=0; i < windings; ++i) { - stbtt__point *p = pts + m; - m += wcount[i]; - j = wcount[i]-1; - for (k=0; k < wcount[i]; j=k++) { - int a=k,b=j; - // skip the edge if horizontal - if (p[j].y == p[k].y) - continue; - // add edge from j to k to the list - e[n].invert = 0; - if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { - e[n].invert = 1; - a=j,b=k; - } - e[n].x0 = p[a].x * scale_x + shift_x; - e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; - e[n].x1 = p[b].x * scale_x + shift_x; - e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; - ++n; - } - } - - // now sort the edges by their highest point (should snap to integer, and then by x) - //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); - stbtt__sort_edges(e, n); - - // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule - stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); - - STBTT_free(e, userdata); -} - -static void stbtt__add_point(stbtt__point *points, int n, float x, float y) -{ - if (!points) return; // during first pass, it's unallocated - points[n].x = x; - points[n].y = y; -} - -// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching -static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) -{ - // midpoint - float mx = (x0 + 2*x1 + x2)/4; - float my = (y0 + 2*y1 + y2)/4; - // versus directly drawn line - float dx = (x0+x2)/2 - mx; - float dy = (y0+y2)/2 - my; - if (n > 16) // 65536 segments on one curve better be enough! - return 1; - if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA - stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x2,y2); - *num_points = *num_points+1; - } - return 1; -} - -static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) -{ - // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough - float dx0 = x1-x0; - float dy0 = y1-y0; - float dx1 = x2-x1; - float dy1 = y2-y1; - float dx2 = x3-x2; - float dy2 = y3-y2; - float dx = x3-x0; - float dy = y3-y0; - float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); - float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); - float flatness_squared = longlen*longlen-shortlen*shortlen; - - if (n > 16) // 65536 segments on one curve better be enough! - return; - - if (flatness_squared > objspace_flatness_squared) { - float x01 = (x0+x1)/2; - float y01 = (y0+y1)/2; - float x12 = (x1+x2)/2; - float y12 = (y1+y2)/2; - float x23 = (x2+x3)/2; - float y23 = (y2+y3)/2; - - float xa = (x01+x12)/2; - float ya = (y01+y12)/2; - float xb = (x12+x23)/2; - float yb = (y12+y23)/2; - - float mx = (xa+xb)/2; - float my = (ya+yb)/2; - - stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x3,y3); - *num_points = *num_points+1; - } -} - -// returns number of contours -static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) -{ - stbtt__point *points=0; - int num_points=0; - - float objspace_flatness_squared = objspace_flatness * objspace_flatness; - int i,n=0,start=0, pass; - - // count how many "moves" there are to get the contour count - for (i=0; i < num_verts; ++i) - if (vertices[i].type == STBTT_vmove) - ++n; - - *num_contours = n; - if (n == 0) return 0; - - *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); - - if (*contour_lengths == 0) { - *num_contours = 0; - return 0; - } - - // make two passes through the points so we don't need to realloc - for (pass=0; pass < 2; ++pass) { - float x=0,y=0; - if (pass == 1) { - points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); - if (points == NULL) goto error; - } - num_points = 0; - n= -1; - for (i=0; i < num_verts; ++i) { - switch (vertices[i].type) { - case STBTT_vmove: - // start the next contour - if (n >= 0) - (*contour_lengths)[n] = num_points - start; - ++n; - start = num_points; - - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x,y); - break; - case STBTT_vline: - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vcurve: - stbtt__tesselate_curve(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - case STBTT_vcubic: - stbtt__tesselate_cubic(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].cx1, vertices[i].cy1, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - } - } - (*contour_lengths)[n] = num_points - start; - } - - return points; -error: - STBTT_free(points, userdata); - STBTT_free(*contour_lengths, userdata); - *contour_lengths = 0; - *num_contours = 0; - return NULL; -} - -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) -{ - float scale = scale_x > scale_y ? scale_y : scale_x; - int winding_count = 0; - int *winding_lengths = NULL; - stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); - if (windings) { - stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); - STBTT_free(winding_lengths, userdata); - STBTT_free(windings, userdata); - } -} - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - int ix0,iy0,ix1,iy1; - stbtt__bitmap gbm; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) { - STBTT_free(vertices, info->userdata); - return NULL; - } - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); - - // now we get the size - gbm.w = (ix1 - ix0); - gbm.h = (iy1 - iy0); - gbm.pixels = NULL; // in case we error - - if (width ) *width = gbm.w; - if (height) *height = gbm.h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); - if (gbm.pixels) { - gbm.stride = gbm.w; - - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); - } - } - STBTT_free(vertices, info->userdata); - return gbm.pixels; -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) -{ - int ix0,iy0; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); - gbm.pixels = output; - gbm.w = out_w; - gbm.h = out_h; - gbm.stride = out_stride; - - if (gbm.w && gbm.h) - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); - - STBTT_free(vertices, info->userdata); -} - -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) -{ - stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); -} - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-CRAPPY packing to keep source code small - -static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata) -{ - float scale; - int x,y,bottom_y, i; - stbtt_fontinfo f; - f.userdata = NULL; - if (!stbtt_InitFont(&f, data, offset)) - return -1; - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - x=y=1; - bottom_y = 1; - - scale = stbtt_ScaleForPixelHeight(&f, pixel_height); - - for (i=0; i < num_chars; ++i) { - int advance, lsb, x0,y0,x1,y1,gw,gh; - int g = stbtt_FindGlyphIndex(&f, first_char + i); - stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); - stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); - gw = x1-x0; - gh = y1-y0; - if (x + gw + 1 >= pw) - y = bottom_y, x = 1; // advance to next row - if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row - return -i; - STBTT_assert(x+gw < pw); - STBTT_assert(y+gh < ph); - stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); - chardata[i].x0 = (stbtt_int16) x; - chardata[i].y0 = (stbtt_int16) y; - chardata[i].x1 = (stbtt_int16) (x + gw); - chardata[i].y1 = (stbtt_int16) (y + gh); - chardata[i].xadvance = scale * advance; - chardata[i].xoff = (float) x0; - chardata[i].yoff = (float) y0; - x = x + gw + 1; - if (y+gh+1 > bottom_y) - bottom_y = y+gh+1; - } - return bottom_y; -} - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) -{ - float d3d_bias = opengl_fillrule ? 0 : -0.5f; - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_bakedchar *b = chardata + char_index; - int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); - int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); - - q->x0 = round_x + d3d_bias; - q->y0 = round_y + d3d_bias; - q->x1 = round_x + b->x1 - b->x0 + d3d_bias; - q->y1 = round_y + b->y1 - b->y0 + d3d_bias; - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// rectangle packing replacement routines if you don't have stb_rect_pack.h -// - -#ifndef STB_RECT_PACK_VERSION - -typedef int stbrp_coord; - -//////////////////////////////////////////////////////////////////////////////////// -// // -// // -// COMPILER WARNING ?!?!? // -// // -// // -// if you get a compile warning due to these symbols being defined more than // -// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // -// // -//////////////////////////////////////////////////////////////////////////////////// - -typedef struct -{ - int width,height; - int x,y,bottom_y; -} stbrp_context; - -typedef struct -{ - unsigned char x; -} stbrp_node; - -struct stbrp_rect -{ - stbrp_coord x,y; - int id,w,h,was_packed; -}; - -static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) -{ - con->width = pw; - con->height = ph; - con->x = 0; - con->y = 0; - con->bottom_y = 0; - STBTT__NOTUSED(nodes); - STBTT__NOTUSED(num_nodes); -} - -static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) -{ - int i; - for (i=0; i < num_rects; ++i) { - if (con->x + rects[i].w > con->width) { - con->x = 0; - con->y = con->bottom_y; - } - if (con->y + rects[i].h > con->height) - break; - rects[i].x = con->x; - rects[i].y = con->y; - rects[i].was_packed = 1; - con->x += rects[i].w; - if (con->y + rects[i].h > con->bottom_y) - con->bottom_y = con->y + rects[i].h; - } - for ( ; i < num_rects; ++i) - rects[i].was_packed = 0; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If -// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) -{ - stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); - int num_nodes = pw - padding; - stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); - - if (context == NULL || nodes == NULL) { - if (context != NULL) STBTT_free(context, alloc_context); - if (nodes != NULL) STBTT_free(nodes , alloc_context); - return 0; - } - - spc->user_allocator_context = alloc_context; - spc->width = pw; - spc->height = ph; - spc->pixels = pixels; - spc->pack_info = context; - spc->nodes = nodes; - spc->padding = padding; - spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; - spc->h_oversample = 1; - spc->v_oversample = 1; - spc->skip_missing = 0; - - stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); - - if (pixels) - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - - return 1; -} - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) -{ - STBTT_free(spc->nodes , spc->user_allocator_context); - STBTT_free(spc->pack_info, spc->user_allocator_context); -} - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) -{ - STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); - STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); - if (h_oversample <= STBTT_MAX_OVERSAMPLE) - spc->h_oversample = h_oversample; - if (v_oversample <= STBTT_MAX_OVERSAMPLE) - spc->v_oversample = v_oversample; -} - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) -{ - spc->skip_missing = skip; -} - -#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) - -static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_w = w - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < h; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < w; ++i) { - STBTT_assert(pixels[i] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i] = (unsigned char) (total / kernel_width); - } - - pixels += stride_in_bytes; - } -} - -static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_h = h - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < w; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < h; ++i) { - STBTT_assert(pixels[i*stride_in_bytes] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - - pixels += 1; - } -} - -static float stbtt__oversample_shift(int oversample) -{ - if (!oversample) - return 0.0f; - - // The prefilter is a box filter of width "oversample", - // which shifts phase by (oversample - 1)/2 pixels in - // oversampled space. We want to shift in the opposite - // direction to counter this. - return (float)-(oversample - 1) / (2.0f * (float)oversample); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k; - int missing_glyph_added = 0; - - k=0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - ranges[i].h_oversample = (unsigned char) spc->h_oversample; - ranges[i].v_oversample = (unsigned char) spc->v_oversample; - for (j=0; j < ranges[i].num_chars; ++j) { - int x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { - rects[k].w = rects[k].h = 0; - } else { - stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - &x0,&y0,&x1,&y1); - rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); - rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); - if (glyph == 0) - missing_glyph_added = 1; - } - ++k; - } - } - - return k; -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, - output, - out_w - (prefilter_x - 1), - out_h - (prefilter_y - 1), - out_stride, - scale_x, - scale_y, - shift_x, - shift_y, - glyph); - - if (prefilter_x > 1) - stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); - - if (prefilter_y > 1) - stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); - - *sub_x = stbtt__oversample_shift(prefilter_x); - *sub_y = stbtt__oversample_shift(prefilter_y); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k, missing_glyph = -1, return_value = 1; - - // save current values - int old_h_over = spc->h_oversample; - int old_v_over = spc->v_oversample; - - k = 0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - float recip_h,recip_v,sub_x,sub_y; - spc->h_oversample = ranges[i].h_oversample; - spc->v_oversample = ranges[i].v_oversample; - recip_h = 1.0f / spc->h_oversample; - recip_v = 1.0f / spc->v_oversample; - sub_x = stbtt__oversample_shift(spc->h_oversample); - sub_y = stbtt__oversample_shift(spc->v_oversample); - for (j=0; j < ranges[i].num_chars; ++j) { - stbrp_rect *r = &rects[k]; - if (r->was_packed && r->w != 0 && r->h != 0) { - stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; - int advance, lsb, x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbrp_coord pad = (stbrp_coord) spc->padding; - - // pad on left and top - r->x += pad; - r->y += pad; - r->w -= pad; - r->h -= pad; - stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); - stbtt_GetGlyphBitmapBox(info, glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - &x0,&y0,&x1,&y1); - stbtt_MakeGlyphBitmapSubpixel(info, - spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w - spc->h_oversample+1, - r->h - spc->v_oversample+1, - spc->stride_in_bytes, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - glyph); - - if (spc->h_oversample > 1) - stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->h_oversample); - - if (spc->v_oversample > 1) - stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->v_oversample); - - bc->x0 = (stbtt_int16) r->x; - bc->y0 = (stbtt_int16) r->y; - bc->x1 = (stbtt_int16) (r->x + r->w); - bc->y1 = (stbtt_int16) (r->y + r->h); - bc->xadvance = scale * advance; - bc->xoff = (float) x0 * recip_h + sub_x; - bc->yoff = (float) y0 * recip_v + sub_y; - bc->xoff2 = (x0 + r->w) * recip_h + sub_x; - bc->yoff2 = (y0 + r->h) * recip_v + sub_y; - - if (glyph == 0) - missing_glyph = j; - } else if (spc->skip_missing) { - return_value = 0; - } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { - ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; - } else { - return_value = 0; // if any fail, report failure - } - - ++k; - } - } - - // restore original values - spc->h_oversample = old_h_over; - spc->v_oversample = old_v_over; - - return return_value; -} - -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) -{ - stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); -} - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) -{ - stbtt_fontinfo info; - int i,j,n, return_value = 1; - //stbrp_context *context = (stbrp_context *) spc->pack_info; - stbrp_rect *rects; - - // flag all characters as NOT packed - for (i=0; i < num_ranges; ++i) - for (j=0; j < ranges[i].num_chars; ++j) - ranges[i].chardata_for_range[j].x0 = - ranges[i].chardata_for_range[j].y0 = - ranges[i].chardata_for_range[j].x1 = - ranges[i].chardata_for_range[j].y1 = 0; - - n = 0; - for (i=0; i < num_ranges; ++i) - n += ranges[i].num_chars; - - rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); - if (rects == NULL) - return 0; - - info.userdata = spc->user_allocator_context; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); - - n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); - - stbtt_PackFontRangesPackRects(spc, rects, n); - - return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); - - STBTT_free(rects, spc->user_allocator_context); - return return_value; -} - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) -{ - stbtt_pack_range range; - range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; - range.array_of_unicode_codepoints = NULL; - range.num_chars = num_chars_in_range; - range.chardata_for_range = chardata_for_range; - range.font_size = font_size; - return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); -} - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) -{ - int i_ascent, i_descent, i_lineGap; - float scale; - stbtt_fontinfo info; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); - scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); - stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); - *ascent = (float) i_ascent * scale; - *descent = (float) i_descent * scale; - *lineGap = (float) i_lineGap * scale; -} - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) -{ - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_packedchar *b = chardata + char_index; - - if (align_to_integer) { - float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); - float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); - q->x0 = x; - q->y0 = y; - q->x1 = x + b->xoff2 - b->xoff; - q->y1 = y + b->yoff2 - b->yoff; - } else { - q->x0 = *xpos + b->xoff; - q->y0 = *ypos + b->yoff; - q->x1 = *xpos + b->xoff2; - q->y1 = *ypos + b->yoff2; - } - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// sdf computation -// - -#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) -#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) - -static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) -{ - float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; - float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; - float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; - float roperp = orig[1]*ray[0] - orig[0]*ray[1]; - - float a = q0perp - 2*q1perp + q2perp; - float b = q1perp - q0perp; - float c = q0perp - roperp; - - float s0 = 0., s1 = 0.; - int num_s = 0; - - if (a != 0.0) { - float discr = b*b - a*c; - if (discr > 0.0) { - float rcpna = -1 / a; - float d = (float) STBTT_sqrt(discr); - s0 = (b+d) * rcpna; - s1 = (b-d) * rcpna; - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { - if (num_s == 0) s0 = s1; - ++num_s; - } - } - } else { - // 2*b*s + c = 0 - // s = -c / (2*b) - s0 = c / (-2 * b); - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - } - - if (num_s == 0) - return 0; - else { - float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); - float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; - - float q0d = q0[0]*rayn_x + q0[1]*rayn_y; - float q1d = q1[0]*rayn_x + q1[1]*rayn_y; - float q2d = q2[0]*rayn_x + q2[1]*rayn_y; - float rod = orig[0]*rayn_x + orig[1]*rayn_y; - - float q10d = q1d - q0d; - float q20d = q2d - q0d; - float q0rd = q0d - rod; - - hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; - hits[0][1] = a*s0+b; - - if (num_s > 1) { - hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; - hits[1][1] = a*s1+b; - return 2; - } else { - return 1; - } - } -} - -static int equal(float *a, float *b) -{ - return (a[0] == b[0] && a[1] == b[1]); -} - -static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) -{ - int i; - float orig[2], ray[2] = { 1, 0 }; - float y_frac; - int winding = 0; - - // make sure y never passes through a vertex of the shape - y_frac = (float) STBTT_fmod(y, 1.0f); - if (y_frac < 0.01f) - y += 0.01f; - else if (y_frac > 0.99f) - y -= 0.01f; - - orig[0] = x; - orig[1] = y; - - // test a ray from (-infinity,y) to (x,y) - for (i=0; i < nverts; ++i) { - if (verts[i].type == STBTT_vline) { - int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; - int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } - if (verts[i].type == STBTT_vcurve) { - int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; - int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; - int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; - int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); - int by = STBTT_max(y0,STBTT_max(y1,y2)); - if (y > ay && y < by && x > ax) { - float q0[2],q1[2],q2[2]; - float hits[2][2]; - q0[0] = (float)x0; - q0[1] = (float)y0; - q1[0] = (float)x1; - q1[1] = (float)y1; - q2[0] = (float)x2; - q2[1] = (float)y2; - if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; - x1 = (int)verts[i ].x; - y1 = (int)verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } else { - int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); - if (num_hits >= 1) - if (hits[0][0] < 0) - winding += (hits[0][1] < 0 ? -1 : 1); - if (num_hits >= 2) - if (hits[1][0] < 0) - winding += (hits[1][1] < 0 ? -1 : 1); - } - } - } - } - return winding; -} - -static float stbtt__cuberoot( float x ) -{ - if (x<0) - return -(float) STBTT_pow(-x,1.0f/3.0f); - else - return (float) STBTT_pow( x,1.0f/3.0f); -} - -// x^3 + a*x^2 + b*x + c = 0 -static int stbtt__solve_cubic(float a, float b, float c, float* r) -{ - float s = -a / 3; - float p = b - a*a / 3; - float q = a * (2*a*a - 9*b) / 27 + c; - float p3 = p*p*p; - float d = q*q + 4*p3 / 27; - if (d >= 0) { - float z = (float) STBTT_sqrt(d); - float u = (-q + z) / 2; - float v = (-q - z) / 2; - u = stbtt__cuberoot(u); - v = stbtt__cuberoot(v); - r[0] = s + u + v; - return 1; - } else { - float u = (float) STBTT_sqrt(-p/3); - float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative - float m = (float) STBTT_cos(v); - float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; - r[0] = s + u * 2 * m; - r[1] = s - u * (m + n); - r[2] = s - u * (m - n); - - //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? - //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); - //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); - return 3; - } -} - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - float scale_x = scale, scale_y = scale; - int ix0,iy0,ix1,iy1; - int w,h; - unsigned char *data; - - if (scale == 0) return NULL; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); - - // if empty, return NULL - if (ix0 == ix1 || iy0 == iy1) - return NULL; - - ix0 -= padding; - iy0 -= padding; - ix1 += padding; - iy1 += padding; - - w = (ix1 - ix0); - h = (iy1 - iy0); - - if (width ) *width = w; - if (height) *height = h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - // invert for y-downwards bitmaps - scale_y = -scale_y; - - { - // distance from singular values (in the same units as the pixel grid) - const float eps = 1./1024, eps2 = eps*eps; - int x,y,i,j; - float *precompute; - stbtt_vertex *verts; - int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); - data = (unsigned char *) STBTT_malloc(w * h, info->userdata); - precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); - - for (i=0,j=num_verts-1; i < num_verts; j=i++) { - if (verts[i].type == STBTT_vline) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; - float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); - precompute[i] = (dist < eps) ? 0.0f : 1.0f / dist; - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; - float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; - float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float len2 = bx*bx + by*by; - if (len2 >= eps2) - precompute[i] = 1.0f / len2; - else - precompute[i] = 0.0f; - } else - precompute[i] = 0.0f; - } - - for (y=iy0; y < iy1; ++y) { - for (x=ix0; x < ix1; ++x) { - float val; - float min_dist = 999999.0f; - float sx = (float) x + 0.5f; - float sy = (float) y + 0.5f; - float x_gspace = (sx / scale_x); - float y_gspace = (sy / scale_y); - - int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path - - for (i=0; i < num_verts; ++i) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - - if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { - float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; - - float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - // coarse culling against bbox - //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && - // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) - dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; - STBTT_assert(i != 0); - if (dist < min_dist) { - // check position along line - // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) - // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) - float dx = x1-x0, dy = y1-y0; - float px = x0-sx, py = y0-sy; - // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy - // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve - float t = -(px*dx + py*dy) / (dx*dx + dy*dy); - if (t >= 0.0f && t <= 1.0f) - min_dist = dist; - } - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; - float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; - float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); - float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); - float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); - float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); - // coarse culling against bbox to avoid computing cubic unnecessarily - if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { - int num=0; - float ax = x1-x0, ay = y1-y0; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float mx = x0 - sx, my = y0 - sy; - float res[3] = {0.f,0.f,0.f}; - float px,py,t,it,dist2; - float a_inv = precompute[i]; - if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula - float a = 3*(ax*bx + ay*by); - float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); - float c = mx*ax+my*ay; - if (STBTT_fabs(a) < eps2) { // if a is 0, it's linear - if (STBTT_fabs(b) >= eps2) { - res[num++] = -c/b; - } - } else { - float discriminant = b*b - 4*a*c; - if (discriminant < 0) - num = 0; - else { - float root = (float) STBTT_sqrt(discriminant); - res[0] = (-b - root)/(2*a); - res[1] = (-b + root)/(2*a); - num = 2; // don't bother distinguishing 1-solution case, as code below will still work - } - } - } else { - float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point - float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; - float d = (mx*ax+my*ay) * a_inv; - num = stbtt__solve_cubic(b, c, d, res); - } - dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { - t = res[0], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { - t = res[1], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { - t = res[2], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - } - } - } - if (winding == 0) - min_dist = -min_dist; // if outside the shape, value is negative - val = onedge_value + pixel_dist_scale * min_dist; - if (val < 0) - val = 0; - else if (val > 255) - val = 255; - data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; - } - } - STBTT_free(precompute, info->userdata); - STBTT_free(verts, info->userdata); - } - return data; -} - -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -////////////////////////////////////////////////////////////////////////////// -// -// font name matching -- recommended not to use this -// - -// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) -{ - stbtt_int32 i=0; - - // convert utf16 to utf8 and compare the results while converting - while (len2) { - stbtt_uint16 ch = s2[0]*256 + s2[1]; - if (ch < 0x80) { - if (i >= len1) return -1; - if (s1[i++] != ch) return -1; - } else if (ch < 0x800) { - if (i+1 >= len1) return -1; - if (s1[i++] != 0xc0 + (ch >> 6)) return -1; - if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; - } else if (ch >= 0xd800 && ch < 0xdc00) { - stbtt_uint32 c; - stbtt_uint16 ch2 = s2[2]*256 + s2[3]; - if (i+3 >= len1) return -1; - c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; - if (s1[i++] != 0xf0 + (c >> 18)) return -1; - if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; - s2 += 2; // plus another 2 below - len2 -= 2; - } else if (ch >= 0xdc00 && ch < 0xe000) { - return -1; - } else { - if (i+2 >= len1) return -1; - if (s1[i++] != 0xe0 + (ch >> 12)) return -1; - if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; - } - s2 += 2; - len2 -= 2; - } - return i; -} - -static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) -{ - return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); -} - -// returns results in whatever encoding you request... but note that 2-byte encodings -// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) -{ - stbtt_int32 i,count,stringOffset; - stbtt_uint8 *fc = font->data; - stbtt_uint32 offset = font->fontstart; - stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return NULL; - - count = ttUSHORT(fc+nm+2); - stringOffset = nm + ttUSHORT(fc+nm+4); - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) - && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { - *length = ttUSHORT(fc+loc+8); - return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); - } - } - return NULL; -} - -static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) -{ - stbtt_int32 i; - stbtt_int32 count = ttUSHORT(fc+nm+2); - stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); - - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - stbtt_int32 id = ttUSHORT(fc+loc+6); - if (id == target_id) { - // find the encoding - stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); - - // is this a Unicode encoding? - if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { - stbtt_int32 slen = ttUSHORT(fc+loc+8); - stbtt_int32 off = ttUSHORT(fc+loc+10); - - // check if there's a prefix match - stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); - if (matchlen >= 0) { - // check for target_id+1 immediately following, with same encoding & language - if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { - slen = ttUSHORT(fc+loc+12+8); - off = ttUSHORT(fc+loc+12+10); - if (slen == 0) { - if (matchlen == nlen) - return 1; - } else if (matchlen < nlen && name[matchlen] == ' ') { - ++matchlen; - if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) - return 1; - } - } else { - // if nothing immediately following - if (matchlen == nlen) - return 1; - } - } - } - - // @TODO handle other encodings - } - } - return 0; -} - -static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) -{ - stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); - stbtt_uint32 nm,hd; - if (!stbtt__isfont(fc+offset)) return 0; - - // check italics/bold/underline flags in macStyle... - if (flags) { - hd = stbtt__find_table(fc, offset, "head"); - if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; - } - - nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return 0; - - if (flags) { - // if we checked the macStyle flags, then just check the family and ignore the subfamily - if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } else { - if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } - - return 0; -} - -static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) -{ - stbtt_int32 i; - for (i=0;;++i) { - stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); - if (off < 0) return off; - if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) - return off; - } -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, - float pixel_height, unsigned char *pixels, int pw, int ph, - int first_char, int num_chars, stbtt_bakedchar *chardata) -{ - return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); -} - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) -{ - return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); -} - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) -{ - return stbtt_GetNumberOfFonts_internal((unsigned char *) data); -} - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) -{ - return stbtt_InitFont_internal(info, (unsigned char *) data, offset); -} - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) -{ - return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); -} - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) -{ - return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -#endif // STB_TRUETYPE_IMPLEMENTATION - - -// FULL VERSION HISTORY -// -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) allow user-defined fabs() replacement -// fix memory leak if fontsize=0.0 -// fix warning from duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// allow PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) -// also more precise AA rasterizer, except if shapes overlap -// remove need for STBTT_sort -// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC -// 1.04 (2015-04-15) typo in example -// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes -// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ -// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match -// non-oversampled; STBTT_POINT_SIZE for packed case only -// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling -// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) -// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID -// 0.8b (2014-07-07) fix a warning -// 0.8 (2014-05-25) fix a few more warnings -// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back -// 0.6c (2012-07-24) improve documentation -// 0.6b (2012-07-20) fix a few more warnings -// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, -// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty -// 0.5 (2011-12-09) bugfixes: -// subpixel glyph renderer computed wrong bounding box -// first vertex of shape can be off-curve (FreeSans) -// 0.4b (2011-12-03) fixed an error in the font baking example -// 0.4 (2011-12-01) kerning, subpixel rendering (tor) -// bugfixes for: -// codepoint-to-glyph conversion using table fmt=12 -// codepoint-to-glyph conversion using table fmt=4 -// stbtt_GetBakedQuad with non-square texture (Zer) -// updated Hello World! sample to use kerning and subpixel -// fixed some warnings -// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) -// userdata, malloc-from-userdata, non-zero fill (stb) -// 0.2 (2009-03-11) Fix unsigned/signed char warnings -// 0.1 (2009-03-09) First public release -// - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/runtime/runtime.cmake b/runtime/runtime.cmake index 603c5b14..a20cc01a 100644 --- a/runtime/runtime.cmake +++ b/runtime/runtime.cmake @@ -95,11 +95,55 @@ else() option(PSX_STATIC_RUNTIME "Statically link SDL2 + libgcc/libstdc++ for a self-contained exe" OFF) endif() -# PSX_LAUNCHER: build the integrated SDL2/OpenGL launcher (settings/disc/memcard/ -# controller front-end shown before the emulator boots). ON by default; the -# oracle/beetle builds never include it. Turn OFF for a launcher-less runtime -# (boots straight in). -option(PSX_LAUNCHER "Build the integrated SDL2/OpenGL launcher UI" ON) +# PSX_LAUNCHER: build the integrated RmlUi launcher UI (settings/disc/memcard/ +# controller front-end shown before the emulator boots). Needs the vendored +# lib/RmlUi + lib/freetype submodules. ON by default; the oracle/beetle builds +# never include it. Turn OFF for a launcher-less runtime (boots straight in). +option(PSX_LAUNCHER "Build the integrated RmlUi launcher UI" ON) + +# Build the vendored RmlUi + FreeType once per CMake project (idempotent — the +# rmlui_core target guards re-entry). Both are linked statically into the exe so +# the self-contained-binary guarantee (PSX_STATIC_RUNTIME) still holds. +function(psxrecomp_ensure_launcher_libs) + if(TARGET rmlui_core) + return() + endif() + if(NOT EXISTS "${PSXRECOMP_ROOT}/lib/RmlUi/CMakeLists.txt" OR + NOT EXISTS "${PSXRECOMP_ROOT}/lib/freetype/CMakeLists.txt") + message(FATAL_ERROR + "PSX_LAUNCHER=ON but lib/RmlUi or lib/freetype is missing. " + "Run: git submodule update --init --recursive") + endif() + + # Static libs fold into the exe (keeps the self-contained binary intact). + set(BUILD_SHARED_LIBS OFF) + + # FreeType — RmlUi's font engine. Disable every optional codec/dep so the + # only thing we pull in is the core font rasteriser. + set(FT_DISABLE_ZLIB TRUE CACHE BOOL "" FORCE) + set(FT_DISABLE_BZIP2 TRUE CACHE BOOL "" FORCE) + set(FT_DISABLE_PNG TRUE CACHE BOOL "" FORCE) + set(FT_DISABLE_HARFBUZZ TRUE CACHE BOOL "" FORCE) + set(FT_DISABLE_BROTLI TRUE CACHE BOOL "" FORCE) + add_subdirectory("${PSXRECOMP_ROOT}/lib/freetype" + "${CMAKE_BINARY_DIR}/_deps/freetype-build" EXCLUDE_FROM_ALL) + # FreeType's add_subdirectory exports the target `freetype` but not the + # namespaced alias (that's install-only). RmlUi's soft dependency check + # looks for Freetype::Freetype, so create it here. + if(NOT TARGET Freetype::Freetype) + add_library(Freetype::Freetype ALIAS freetype) + endif() + + # RmlUi — core only, FreeType font engine, no samples/tests/PCH. + set(RMLUI_SAMPLES OFF CACHE BOOL "" FORCE) + set(RMLUI_FONT_ENGINE "freetype" CACHE STRING "" FORCE) + set(RMLUI_PRECOMPILED_HEADERS OFF CACHE BOOL "" FORCE) + # The bundled robin_hood hash map fails its bitness detection under MinGW + # GCC ("#error Unsupported bitness"); use std:: containers instead. + set(RMLUI_THIRDPARTY_CONTAINERS OFF CACHE BOOL "" FORCE) + add_subdirectory("${PSXRECOMP_ROOT}/lib/RmlUi" + "${CMAKE_BINARY_DIR}/_deps/RmlUi-build" EXCLUDE_FROM_ALL) +endfunction() set(PSXRECOMP_RUNTIME_SOURCES ${PSXRECOMP_ROOT}/runtime/src/main.cpp @@ -410,17 +454,31 @@ function(psxrecomp_add_runtime_target target) target_compile_definitions(${target} PRIVATE PSX_COSIM=1 PSX_NO_DEBUG_TOOLS=1) endif() - # Integrated SDL2/OpenGL launcher (not in the oracle build — headless). + # Integrated RmlUi launcher (not in the oracle build — that's headless). if(PSX_LAUNCHER AND NOT PSXRT_ORACLE) + psxrecomp_ensure_launcher_libs() target_sources(${target} PRIVATE ${PSXRECOMP_ROOT}/runtime/launcher/launcher.cpp ${PSXRECOMP_ROOT}/runtime/launcher/stb_image_impl.cpp + ${PSXRECOMP_ROOT}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp + ${PSXRECOMP_ROOT}/lib/RmlUi/Backends/RmlUi_Renderer_GL3.cpp ) + # The vendored GL3 backend uses std::all_of without including + # ; force-include it rather than patching the pinned + # submodule. (GCC/Clang only; MSVC pulls it in transitively.) + if(NOT MSVC) + set_source_files_properties( + ${PSXRECOMP_ROOT}/lib/RmlUi/Backends/RmlUi_Renderer_GL3.cpp + PROPERTIES COMPILE_OPTIONS "-include;algorithm") + endif() target_include_directories(${target} PRIVATE + ${PSXRECOMP_ROOT}/lib/RmlUi/Include + ${PSXRECOMP_ROOT}/lib/RmlUi/Backends ${PSXRECOMP_ROOT}/runtime/launcher ) + target_link_libraries(${target} PRIVATE RmlUi::Core) target_compile_definitions(${target} PRIVATE PSX_LAUNCHER=1) - # Ship the launcher assets (fonts + images) next to the exe. + # Ship the launcher assets (RML/RCSS/fonts) next to the exe. add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PSXRECOMP_ROOT}/runtime/launcher/assets" diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index dcc8312f..b41540d4 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -10084,9 +10084,7 @@ static void handle_sljit_status(int id, const char *json) int selftest = overlay_sljit_selftest(); int available = 0, st_ok = 0; unsigned long long compiles = 0, declines = 0, bytes = 0; -// Sorry but i cant compile with this -// overlay_sljit_get_status(&available, &st_ok, &compiles, &declines, &bytes); - overlay_sljit_get_status(&available, &st_ok, (uint64_t *)&compiles, (uint64_t *)&declines, (uint64_t *)&bytes); + overlay_sljit_get_status(&available, &st_ok, &compiles, &declines, &bytes); send_fmt("{\"id\":%d,\"ok\":true,\"backend\":\"%s\",\"available\":%d," "\"selftest_ok\":%d,\"live\":%d,\"compiles\":%llu,\"declines\":%llu," "\"bytes_emitted\":%llu,\"shards_registered\":%u,\"obsoleted\":%u,"