diff --git a/source/src/console.h b/source/src/console.h index f238acd7..cd00e304 100644 --- a/source/src/console.h +++ b/source/src/console.h @@ -32,7 +32,7 @@ template struct consolebuffer struct textinputbuffer { - string buf; + string buf; int pos, max; textinputbuffer() : pos(-1), max(0) @@ -58,31 +58,57 @@ struct textinputbuffer case SDLK_DELETE: { - int len = (int)strlen(buf); - if(pos<0) break; - memmove(&buf[pos], &buf[pos+1], len - pos); + if(pos < 0) break; + + std::string s(buf); + int len = utf8::distance(s.begin(), s.end()); + std::string::iterator first = s.begin(); + std::string::iterator second = s.begin(); + utf8::advance(first, pos, s.end()); + utf8::advance(second, pos + 1, s.end()); + s.erase(first, second); + strcpy(buf, s.c_str()); + if(pos>=len-1) pos = -1; return true; } case SDLK_BACKSPACE: { - int len = (int)strlen(buf), i = pos>=0 ? pos : len; - if(i<1) break; - memmove(&buf[i-1], &buf[i], len - i + 1); - if(pos>0) pos--; - else if(!pos && len<=1) pos = -1; + std::string s(buf); + int len = utf8::distance(s.begin(), s.end()); + int i = pos>=0 ? pos : len; + if(i<1) break; + + std::string::iterator first = s.begin(); + std::string::iterator second = s.begin(); + utf8::advance(first, i - 1, s.end()); + utf8::advance(second, i, s.end()); + s.erase(first, second); + strcpy(buf, s.c_str()); + + if(pos > 0) pos--; + else if(!pos && len <=1 ) pos = -1; return true; } case SDLK_LEFT: + { + std::string s(buf); + int len = utf8::distance(s.begin(), s.end()); + if(pos > 0) pos--; - else if(pos < 0) pos = (int)strlen(buf)-1; + else if(pos < 0) pos = len-1; break; + } case SDLK_RIGHT: - if(pos>=0 && ++pos>=(int)strlen(buf)) pos = -1; + { + std::string s(buf); + int len = utf8::distance(s.begin(), s.end()); + if(pos>=0 && ++pos >= len) pos = -1; break; + } case SDLK_v: extern void pasteconsole(char *dst); @@ -102,19 +128,30 @@ struct textinputbuffer { if(unicode) { - size_t len = strlen(buf); - if(max && (int)len>=max) break; - if(len+1 < sizeof(buf)) - { - if(pos < 0) buf[len] = unicode; - else - { - memmove(&buf[pos+1], &buf[pos], len - pos); - buf[pos++] = unicode; - } - buf[len+1] = '\0'; - return true; - } + std::string s(buf); + char tmp[5] = { 0, 0, 0, 0, 0 }; + utf8::append(unicode, tmp); + std::string newchar(tmp); + + int len = utf8::distance(s.begin(), s.end()); + if(max && (int)len>=max) break; + + if(s.length() + newchar.length() < sizeof(buf)) + { + if(pos < 0) + { + s.append(newchar); + } + else + { + std::string::iterator first = s.begin(); + utf8::advance(first, pos + 1, s.end()); + s.insert(first, newchar.begin(), newchar.end()); + pos++; + } + strcpy(buf, s.c_str()); + return true; + } } break; } diff --git a/source/src/i18n.cpp b/source/src/i18n.cpp index 693267bd..ee335219 100644 --- a/source/src/i18n.cpp +++ b/source/src/i18n.cpp @@ -8,8 +8,15 @@ i18nmanager::i18nmanager(const char *domain, const char *basepath) : domain(doma locale = setlocale(LC_ALL, ""); // use current default locale bindtextdomain(domain, basepath); // set base path textdomain(domain); + bind_textdomain_codeset(domain, "UTF-8"); // we use the utf-8 charset only printf("current locale: %s\n", locale); + + const char *test = _("About"); + std::string test2(test); + bool valid = utf8::is_valid(test2.begin(), test2.end()); + + printf("is valid: %s", valid ? "true" : "false"); } // export gettext to cubescript diff --git a/source/src/i18n.h b/source/src/i18n.h index 31370303..ab2bd12a 100644 --- a/source/src/i18n.h +++ b/source/src/i18n.h @@ -10,3 +10,8 @@ struct i18nmanager i18nmanager(const char *domain, const char *basepath); // initialize locale system }; + +enum { CF_NONE = 0, CF_OK, CF_FAIL, CF_SIZE }; + +#define FONTSTART 33 +#define FONTCHARS 94 diff --git a/source/src/main.cpp b/source/src/main.cpp index fad9fa10..c82f61c3 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -786,6 +786,9 @@ int main(int argc, char **argv) int usedcolorbits = 0, useddepthbits = 0, usedfsaa = 0; setupscreen(usedcolorbits, useddepthbits, usedfsaa); + initlog("font"); + initfont(); + initlog("video: misc"); SDL_WM_SetCaption("AssaultCube", NULL); keyrepeat(false); diff --git a/source/src/platform.h b/source/src/platform.h index e8674c23..7587e1ad 100644 --- a/source/src/platform.h +++ b/source/src/platform.h @@ -44,6 +44,9 @@ #ifndef STANDALONE #include #include + #include + + #include #define GL_GLEXT_LEGACY #define __glext_h__ diff --git a/source/src/protos.h b/source/src/protos.h index 8618c447..55b1e7e1 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -304,6 +304,8 @@ extern Texture *lookuptexture(int tex, Texture *failtex = notexture); extern bool reloadtexture(Texture &t); extern bool reloadtexture(const char *name); extern void reloadtextures(); +Texture *createtexturefromsurface(const char *name, SDL_Surface *s); +extern void blitsurface(SDL_Surface *dst, SDL_Surface *src, int x, int y); static inline Texture *lookupworldtexture(int tex) { return lookuptexture(tex, noworldtexture); } @@ -499,6 +501,11 @@ struct font short x, y, w, h; }; + struct utf8charinfo : charinfo + { + int code; + }; + char *name; Texture *tex; vector chars; @@ -514,6 +521,7 @@ struct font extern int VIRTW; // virtual screen size for text & HUD extern font *curfont; +extern void initfont(); extern bool setfont(const char *name); extern font *getfont(const char *name); extern void pushfont(const char *name); diff --git a/source/src/rendertext.cpp b/source/src/rendertext.cpp index fa050051..e79f7f19 100644 --- a/source/src/rendertext.cpp +++ b/source/src/rendertext.cpp @@ -32,6 +32,8 @@ void newfont(char *name, char *tex, char *defaultw, char *defaulth, char *offset fontdef = f; } +extern GLenum texformat(int bpp); + void fontchar(int x, int y, int w, int h) { if(!fontdef) return; @@ -93,23 +95,203 @@ void draw_textf(const char *fstr, int left, int top, ...) draw_text(str, left, top); } + + +extern int shdsize, outline, win32msg; + + +#if SDL_BYTEORDER == SDL_BIG_ENDIAN +#define RMASK 0xff000000 +#define GMASK 0x00ff0000 +#define BMASK 0x0000ff00 +#define AMASK 0x000000ff +#else +#define RMASK 0x000000ff +#define GMASK 0x0000ff00 +#define BMASK 0x00ff0000 +#define AMASK 0xff000000 +#endif + +// ringbuf for utf8 character storage +struct charringbuf : ringbuf +{ + // find by character code + int findbycharcode(int code) + { + loopi(len) + { + if(data[i].code == code) + return i; + } + + return -1; + } +}; + +TTF_Font *ttffont = NULL; +font utf8font; +charringbuf utf8chars; + +void initfont() +{ + static bool initialized = false; + if(!initialized) + { + TTF_Init(); + + int fsize = 64; + char *fonname = "packages\\misc\\font.ttf"; + ttffont = TTF_OpenFont(findfile(fonname, "r"), fsize); + + utf8font.defaulth = 0; + utf8font.defaultw = 0; + utf8font.offsetw = 0; + utf8font.offseth = 10; + utf8font.offsetx = 0; + utf8font.offsety = 0; + + initialized = true; + } +} + +void createutf8charset() +{ + int isize = 512; + + SDL_Surface *charsetsurface = SDL_CreateRGBSurface(SDL_SWSURFACE, isize, isize, 32, RMASK, GMASK, BMASK, AMASK); + + if(charsetsurface) + { + SDL_Color color; + color.r = 255; + color.g = 255; + color.b = 255; + + int posx = 0; + int posy = 0; + + loopv(utf8chars) + { + font::utf8charinfo &charinfo = utf8chars[i]; + int code = charinfo.code; + + char u[5] = {0,0,0,0,0}; + utf8::append(code, u); + + SDL_Surface *fontsurface = TTF_RenderUTF8_Blended(ttffont, u, color); + + // update row/column info + if(posx + fontsurface->w > charsetsurface->w) + { + posx = 0; + posy += fontsurface->h; // fixme + } + if(posy + fontsurface->h > charsetsurface->h) + break; + + // blit onto charset + SDL_SetAlpha(fontsurface, 0, 0); + blitsurface(charsetsurface, fontsurface, posx, posy); + + // update charinfo properties + charinfo.x = posx; + charinfo.y = posy; + charinfo.w = fontsurface->w; + charinfo.h = fontsurface->h; + + posx += fontsurface->w; + SDL_FreeSurface(fontsurface); + } + + utf8font.tex = createtexturefromsurface("utf8charset", charsetsurface); + + //extern void savepng(SDL_Surface *s, const char *name); + //savepng(t, "font.png"); + + SDL_FreeSurface(charsetsurface); + } +} + +void addutf8char(int code) +{ + // add to buf + font::utf8charinfo charinfo; + charinfo.code = code; + utf8chars.add(charinfo); + + // update charset + createutf8charset(); +} + +font::charinfo *loadchar(int code) +{ + int idx = utf8chars.findbycharcode(code); + if(idx >= 0) + return &utf8chars[idx]; + + // add + addutf8char(code); + + idx = utf8chars.findbycharcode(code); + return &utf8chars[idx]; +} + +int draw_char(font &f, font::charinfo &info, int charcode, int x, int y) +{ + // fixme + glEnd(); + glBindTexture(GL_TEXTURE_2D, f.tex->id); + glBegin(GL_QUADS); + + float tc_left = (info.x + f.offsetx) / float(f.tex->xs); + float tc_top = (info.y + f.offsety) / float(f.tex->ys); + float tc_right = (info.x + info.w + f.offsetw) / float(f.tex->xs); + float tc_bottom = (info.y + info.h + f.offseth) / float(f.tex->ys); + + glTexCoord2f(tc_left, tc_top ); glVertex2f(x, y); + glTexCoord2f(tc_right, tc_top ); glVertex2f(x + info.w, y); + glTexCoord2f(tc_right, tc_bottom); glVertex2f(x + info.w, y + info.h); + glTexCoord2f(tc_left, tc_bottom); glVertex2f(x, y + info.h); + + xtraverts += 4; + return info.w; +} + +// fixme +font::charinfo &getcharinfo(int c) +{ + if(curfont->chars.inrange(c-curfont->skip)) + { + font::charinfo &info = curfont->chars[c-curfont->skip]; + return info; + } + else + { + font::charinfo &info = *loadchar(c); + return info; + } +} + static int draw_char(int c, int x, int y) { - font::charinfo &info = curfont->chars[c-curfont->skip]; - float tc_left = (info.x + curfont->offsetx) / float(curfont->tex->xs); - float tc_top = (info.y + curfont->offsety) / float(curfont->tex->ys); - float tc_right = (info.x + info.w + curfont->offsetw) / float(curfont->tex->xs); - float tc_bottom = (info.y + info.h + curfont->offseth) / float(curfont->tex->ys); + if(curfont->chars.inrange(c-curfont->skip)) + { + font::charinfo &info = curfont->chars[c-curfont->skip]; - glTexCoord2f(tc_left, tc_top ); glVertex2f(x, y); - glTexCoord2f(tc_right, tc_top ); glVertex2f(x + info.w, y); - glTexCoord2f(tc_right, tc_bottom); glVertex2f(x + info.w, y + info.h); - glTexCoord2f(tc_left, tc_bottom); glVertex2f(x, y + info.h); + return draw_char(*curfont, info, c, x, y); + } + else + { + // fixme + glEnd(); + font::charinfo &info = *loadchar(c); + glBegin(GL_QUADS); - xtraverts += 4; - return info.w; + return draw_char(utf8font, info, c, x, y); + } } + //stack[sp] is current color index static void text_color(char c, char *stack, int size, int &sp, bvec color, int a) { @@ -297,15 +479,228 @@ void draw_text(const char *str, int left, int top, int r, int g, int b, int a, i #define TEXTCOLOR(idx) text_color(str[idx], colorstack, sizeof(colorstack), colorpos, color, a); #define TEXTCHAR(idx) x += draw_char(c, left+x, top+y)+1; #define TEXTWORD TEXTWORDSKELETON + char colorstack[10]; bvec color(r, g, b); int colorpos = 0, cx = INT_MIN, cy = 0, cc = ' '; colorstack[0] = 'c'; //indicate user color - glBlendFunc(GL_SRC_ALPHA, curfont->tex->bpp==32 ? GL_ONE_MINUS_SRC_ALPHA : GL_ONE); - glBindTexture(GL_TEXTURE_2D, curfont->tex->id); + + glBlendFunc(GL_SRC_ALPHA, curfont->tex->bpp==32 ? GL_ONE_MINUS_SRC_ALPHA : GL_ONE); + glBindTexture(GL_TEXTURE_2D, curfont->tex->id); + glBegin(GL_QUADS); glColor4ub(color.x, color.y, color.z, a); - TEXTSKELETON + + std::string text(str); + std::string::iterator begin = text.begin(); + std::string::iterator end = text.end(); + std::string::iterator cursoriter = end; + if(cursor >= 0 && cursor < utf8::distance(begin, end)) + { + cursoriter = begin; + utf8::advance(cursoriter, cursor, end); + } + + /* WIP ALERT */ + + int y = 0, x = 0, col = 0, colx = 0; + + for(std::string::iterator iter = text.begin(); iter != text.end(); utf8::next(iter, text.end())) + { + //TEXTINDEX(i) + //if(idx == cursor) { cx = x; cy = y; cc = str[idx]; } + + int c = utf8::peek_next(iter, text.end()); + //int c = str[i]; + + if(iter == cursoriter) + { + cx = x; + cy = y; + cc = c; + } + + if(c=='\t') + { + //TEXTTAB(i); + if(columns && collength()) + { + colx += (*columns)[col++]; + x = colx; + } + else x = TABALIGN(x); + + //TEXTWHITE(i) + } + else if(c==' ') + { + x += curfont->defaultw; + //TEXTWHITE(i) + } + else if(c=='\n') + { + //TEXTLINE(i) + x = 0; + y += FONTH; + } + else if(c=='\f') + { + /* + if(str[i+1]) + { + i++; + //TEXTCOLOR(i) + text_color(str[i], colorstack, sizeof(colorstack), colorpos, color, a); + } + */ + + std::string::iterator test = iter; + test++; + if(test != end) + { + //TEXTCOLOR(i) + c = utf8::next(iter, end); + text_color(c, colorstack, sizeof(colorstack), colorpos, color, a); + } + } + else if(c=='\a') + { + /* + if(str[i+1]) + { + i++; + } + */ + + std::string::iterator next = iter; + next++; + if(next != end) + { + iter++; + } + + } + else if(curfont->chars.inrange(c-curfont->skip)) + { + font::charinfo &cinfo = getcharinfo(c); + + if(maxwidth != -1) + { + /* ORIGINAL, UNWRAPPED CPP TEMPLATES + int j = i; + int w = cinfo.w; //curfont->chars[c-curfont->skip].w; + + for(; str[i+1]; i++) + { + int c = str[i+1]; + if(c=='\f') { if(str[i+2]) i++; continue; } + if(i-j > 16) break; + if(!curfont->chars.inrange(c-curfont->skip)) break; + int cw = curfont->chars[c-curfont->skip].w + 1; + if(w + cw >= maxwidth) break; + w += cw; + } + if(x + w >= maxwidth && j!=0) + { + //TEXTLINE(j-1) + x = 0; y += FONTH; + } + + //TEXTWORD + for(; j <= i; j++) + { + //TEXTINDEX(j) + if(j == cursor) { cx = x; cy = y; cc = str[j]; } + int c = str[j]; + if(c=='\f') + { + if(str[j+1]) + { + j++; + //TEXTCOLOR(j) + text_color(str[j], colorstack, sizeof(colorstack), colorpos, color, a); + } + } + else + { + //TEXTCHAR(j) + x += draw_char(c, left+x, top+y)+1; + } + } + */ + + std::string::iterator next = iter; + int w = cinfo.w; //curfont->chars[c-curfont->skip].w; + + do + { + std::string::iterator test = iter; + int c = utf8::next(test, end); + if(test == end) break; + + if(c=='\f') + { + std::string::iterator test = iter; + utf8::advance(test, 2, end); + if(test != end) + { + utf8::next(iter, end); + } + continue; + } + if(utf8::distance(iter, next) > 16) break; + //if(!curfont->chars.inrange(c-curfont->skip)) fixme + if(c < curfont->skip) // fixme + { + break; + } + int cw = getcharinfo(c).w + 1; + if(w + cw >= maxwidth) break; + w += cw; + + utf8::next(iter, end); + + } while(true); + + if(x + w >= maxwidth && next != begin) //fixme + { + x = 0; + y += FONTH; + } + + for(; next <= iter && next != end; ) + { + int test = utf8::distance(next, iter); + + int c = utf8::peek_next(next, end); + if(next == cursoriter) { cx = x; cy = y; cc = c; } + + if(c=='\f') + { + std::string::iterator test = next; + utf8::next(test, end); + if(test != end) + { + c = utf8::next(next, end); + text_color(c, colorstack, sizeof(colorstack), colorpos, color, a); + } + } + else + { + x += draw_char(c, left+x, top+y)+1; + } + + utf8::next(next, end); + } + + } + else + { + x += draw_char(c, left+x, top+y)+1; + } + } + } + glEnd(); if(cursor >= 0) { @@ -314,6 +709,7 @@ void draw_text(const char *str, int left, int top, int r, int g, int b, int a, i int cw = curfont->chars.inrange(cc-curfont->skip) ? curfont->chars[cc-curfont->skip].w + 1 : curfont->defaultw; rendercursor(left+cx, top+cy, cw); } + #undef TEXTINDEX #undef TEXTTAB #undef TEXTWHITE @@ -329,4 +725,3 @@ void reloadfonts() if(!reloadtexture(*f.tex)) fatal("failed to reload font texture"); ); } - diff --git a/source/src/texture.cpp b/source/src/texture.cpp index c55e9ce2..70735d9d 100644 --- a/source/src/texture.cpp +++ b/source/src/texture.cpp @@ -348,6 +348,34 @@ Texture *textureload(const char *name, int clamp, bool mipmap, bool canreduce) return t; } +Texture *createtexturefromsurface(const char *name, SDL_Surface *s) +{ + string pname; + copystring(pname, name); + path(pname); + Texture *t = textures.access(pname); + if(!t) + { + char *key = newstring(pname); + t = &textures[key]; + t->name = key; + } + + GLuint tnum; + glGenTextures(1, &tnum); + GLenum format = texformat(s->format->BitsPerPixel); + createtexture(tnum, s->w, s->h, s->pixels, 0, true, false, format); + + t->xs = s->w; + t->ys = s->h; + t->bpp = s->format->BitsPerPixel; + t->clamp = 0; + t->mipmap = true; + t->canreduce = false; + t->id = tnum; + return t; +} + struct Slot { string name; @@ -665,3 +693,28 @@ void cleanuptmus() loopi(MAXTMUS) tmus[i] = invalidtmu; } + +// only works on 32 bit surfaces with alpha in 4th byte! +void blitsurface(SDL_Surface *dst, SDL_Surface *src, int x, int y) +{ + uchar *dstp = (uchar *)dst->pixels + y*dst->pitch + x*4, + *srcp = (uchar *)src->pixels; + int dstpitch = dst->pitch - 4*src->w, + srcpitch = src->pitch - 4*src->w; + loop(dy, src->h) + { + loop(dx, src->w) + { + uint k1 = (255U - srcp[3]) * dstp[3], k2 = srcp[3] * 255U, kmax = max(dstp[3], srcp[3]), kscale = max(kmax * 255U, 1U); + dstp[0] = (dstp[0]*k1 + srcp[0]*k2) / kscale; + dstp[1] = (dstp[1]*k1 + srcp[1]*k2) / kscale; + dstp[2] = (dstp[2]*k1 + srcp[2]*k2) / kscale; + dstp[3] = kmax; + dstp += 4; + srcp += 4; + } + dstp += dstpitch; + srcp += srcpitch; + } +} + diff --git a/source/src/tools.h b/source/src/tools.h index d588efd8..1f494834 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -690,6 +690,12 @@ template struct ringbuf if(i >= SIZE) i -= SIZE; return data[i]; } + + int find(const T &o) + { + loopi(len) if(data[i]==o) return i; + return -1; + } }; #define itoa(s, i) sprintf(s, "%d", i)