From: blub Date: Wed, 23 Dec 2009 10:43:52 +0000 (+0000) Subject: Adding FreeType2 and UTF-8 Support. X-Git-Tag: xonotic-v0.1.0preview~1007 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fdarkplaces.git;a=commitdiff_plain;h=9b4696df9959f0c951ce4ab7a26323b41d0cc4a2 Adding FreeType2 and UTF-8 Support. UTF-8 is disabled by default, FreeType is enabled. new cvars: utf8_enable (0) r_font_disable_freetype (1) r_font_use_alpha_textures (0, not really finished yet) r_font_size_snapping (1 - 0 looks bad when vid_conwidth/height and vid_width/height are too different) git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@9641 d7cf8633-e32d-0410-b094-e92efae38249 --- diff --git a/common.c b/common.c index c002be8d..70f36e71 100644 --- a/common.c +++ b/common.c @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // common.c -- misc functions used in client and server #include "quakedef.h" +#include "utf8lib.h" #include #include @@ -696,6 +697,7 @@ int COM_Wordwrap(const char *string, size_t length, float continuationWidth, flo int result = 0; size_t wordLen; size_t dummy; + size_t wordChars; dummy = 0; wordWidth(passthroughCW, NULL, &dummy, -1); @@ -708,12 +710,12 @@ int COM_Wordwrap(const char *string, size_t length, float continuationWidth, flo switch(ch) { case 0: // end of string - result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + result += processLine(passthroughPL, startOfLine, u8_strnlen(startOfLine, cursor - startOfLine), spaceUsedInLine, isContinuation); isContinuation = false; goto out; break; case '\n': // end of line - result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + result += processLine(passthroughPL, startOfLine, u8_strnlen(startOfLine, cursor - startOfLine), spaceUsedInLine, isContinuation); isContinuation = false; ++cursor; startOfLine = cursor; @@ -738,8 +740,9 @@ int COM_Wordwrap(const char *string, size_t length, float continuationWidth, flo } } out_inner: - spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordLen, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line - if(wordLen < 1) + wordChars = strnlen(cursor, wordLen); + spaceUsedForWord = wordWidth(passthroughCW, cursor, &wordChars, maxWidth - continuationWidth); // this may have reduced wordLen when it won't fit - but this is GOOD. TODO fix words that do fit in a non-continuation line + if(wordChars < 1) { wordLen = 1; spaceUsedForWord = maxWidth + 1; // too high, forces it in a line of itself @@ -753,7 +756,7 @@ int COM_Wordwrap(const char *string, size_t length, float continuationWidth, flo else { // output current line - result += processLine(passthroughPL, startOfLine, cursor - startOfLine, spaceUsedInLine, isContinuation); + result += processLine(passthroughPL, startOfLine, u8_strnlen(startOfLine, cursor - startOfLine), spaceUsedInLine, isContinuation); isContinuation = true; startOfLine = cursor; cursor += wordLen; diff --git a/console.c b/console.c index 71a5f869..6a59b970 100644 --- a/console.c +++ b/console.c @@ -26,6 +26,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif #include +// for u8_encodech +#include "ft2.h" + float con_cursorspeed = 4; // lines up from bottom to display @@ -1363,7 +1366,24 @@ void Con_DrawInput (void) // add the cursor frame if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible - text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right + { + if (!utf8_enable.integer) + text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right + else if (y + 3 < (int)sizeof(editlinecopy)-1) + { + int ofs = u8_bytelen(text + key_linepos, 1); + size_t len; + const char *curbuf; + curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len); + + if (curbuf) + { + memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len); + memcpy(text + key_linepos, curbuf, len); + } + } else + text[key_linepos] = '-' + ('+' - '-') * key_insert; + } // text[key_linepos + 1] = 0; @@ -1402,10 +1422,16 @@ float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float ti->colorindex = -1; return ti->fontsize * ti->font->maxwidth; } + /* if(maxWidth >= 0) return DrawQ_TextWidth_Font_UntilWidth(w, length, false, ti->font, maxWidth / ti->fontsize) * ti->fontsize; else if(maxWidth == -1) return DrawQ_TextWidth_Font(w, *length, false, ti->font) * ti->fontsize; + */ + if(maxWidth >= 0) + return DrawQ_TextWidth_Font_UntilWidth_Size(w, ti->fontsize, ti->fontsize, length, false, ti->font, maxWidth); + else if(maxWidth == -1) + return DrawQ_TextWidth_Font_Size(w, ti->fontsize, ti->fontsize, *length, false, ti->font); else { printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth); @@ -1590,20 +1616,23 @@ void Con_DrawNotify (void) if(numChatlines) { v = chatstart + numChatlines * con_chatsize.value; - Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, "^3\014\014\014 "); // 015 is ·> character in conchars.tga + Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, con_chatsize.value, 0.0, 1.0, /*"^3\014\014\014 "*/ "^3\xee\x80\x8d\xee\x80\x8d\xee\x80\x8d "); // 015 is ·> character in conchars.tga } if (key_dest == key_message) { + //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on } int colorindex = -1; + const char *cursor; + cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL); // LordHavoc: speedup, and other improvements if (chat_mode < 0) - dpsnprintf(temptext, sizeof(temptext), "]%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1)); + dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor); else if(chat_mode) - dpsnprintf(temptext, sizeof(temptext), "say_team:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1)); + dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor); else - dpsnprintf(temptext, sizeof(temptext), "say:%s%c", chat_buffer, (int) 10+((int)(realtime*con_cursorspeed)&1)); + dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor); // FIXME word wrap inputsize = (numChatlines ? con_chatsize : con_notifysize).value; diff --git a/draw.h b/draw.h index e3651266..114f90cc 100644 --- a/draw.h +++ b/draw.h @@ -90,6 +90,8 @@ DRAWFLAG_MASK = 0xFF, // ONLY R_BeginPolygon() DRAWFLAG_MIPMAP = 0x100 // ONLY R_BeginPolygon() }; +#define MAX_FONT_SIZES 8 +#define MAX_FONT_FALLBACKS 3 typedef struct dp_font_s { rtexture_t *tex; @@ -98,6 +100,12 @@ typedef struct dp_font_s float scale; // scales the font (without changing line height!) char texpath[MAX_QPATH]; char title[MAX_QPATH]; + + int req_face; // requested face index, usually 0 + float req_sizes[MAX_FONT_SIZES]; // sizes to render the font with, 0 still defaults to 16 (backward compatibility when loadfont doesn't get a size parameter) and -1 = disabled + char fallbacks[MAX_FONT_FALLBACKS][MAX_QPATH]; + int fallback_faces[MAX_FONT_FALLBACKS]; + struct ft2_font_s *ft2; } dp_font_t; @@ -137,9 +145,13 @@ void DrawQ_Fill(float x, float y, float width, float height, float red, float gr // if r_textshadow is not zero, an additional instance of the text is drawn first at an offset with an inverted shade of gray (black text produces a white shadow, brightly colored text produces a black shadow) float DrawQ_String(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes); float DrawQ_String_Font(float x, float y, const char *text, size_t maxlen, float scalex, float scaley, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt); -float DrawQ_TextWidth_Font(const char *text, size_t maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt); -float DrawQ_TextWidth_Font_UntilWidth(const char *text, size_t *maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth); -float DrawQ_TextWidth_Font_UntilWidth_TrackColors(const char *text, size_t *maxlen, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); +// you are STRONGLY DISCOURAGED to use a version without the _Size suffix!!! +/* don't use: */float DrawQ_TextWidth_Font(const char *text, size_t maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt); +/* use this: */float DrawQ_TextWidth_Font_Size(const char *text, float w, float h, size_t maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt); +/* don't use: */float DrawQ_TextWidth_Font_UntilWidth(const char *text, size_t *maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth); +/* use this: */float DrawQ_TextWidth_Font_UntilWidth_Size(const char *text, float w, float h, size_t *maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth); +/* don't use: */float DrawQ_TextWidth_Font_UntilWidth_TrackColors(const char *text, size_t *maxlen, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); +/* use this: */float DrawQ_TextWidth_Font_UntilWidth_TrackColors_Size(const char *text, float w, float h, size_t *maxlen, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth); // draw a very fancy pic (per corner texcoord/color control), the order is tl, tr, bl, br void DrawQ_SuperPic(float x, float y, cachepic_t *pic, float width, float height, float s1, float t1, float r1, float g1, float b1, float a1, float s2, float t2, float r2, float g2, float b2, float a2, float s3, float t3, float r3, float g3, float b3, float a3, float s4, float t4, float r4, float g4, float b4, float a4, int flags); // draw a triangle mesh diff --git a/gl_draw.c b/gl_draw.c index 691a7b29..66b2b89e 100644 --- a/gl_draw.c +++ b/gl_draw.c @@ -25,6 +25,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "cl_video.h" #include "cl_dyntexture.h" +#include "ft2.h" +#include "ft2_fontdefs.h" + dp_font_t dp_fonts[MAX_FONTS] = {{0}}; cvar_t r_textshadow = {CVAR_SAVE, "r_textshadow", "0", "draws a shadow on all text to improve readability (note: value controls offset, 1 = 1 pixel, 1.5 = 1.5 pixels, etc)"}; @@ -544,6 +547,20 @@ static void LoadFont(qboolean override, const char *name, dp_font_t *fnt) if(drawtexturepool == NULL) return; // before gl_draw_start, so will be loaded later + if(fnt->ft2) + { + // clear freetype font + Font_UnloadFont(fnt->ft2); + Mem_Free(fnt->ft2); + fnt->ft2 = NULL; + } + + if(fnt->req_face != -1) + { + if(!Font_LoadFont(fnt->texpath, fnt)) + Con_Printf("Failed to load font-file for '%s', it will not support as many characters.\n", fnt->texpath); + } + fnt->tex = Draw_CachePic_Flags(fnt->texpath, CACHEPICFLAG_QUIET | CACHEPICFLAG_NOCOMPRESSION)->tex; if(fnt->tex == r_texture_notexture) { @@ -635,15 +652,54 @@ static dp_font_t *FindFont(const char *title) return NULL; } +static inline float snap_to_pixel_x(float x, float roundUpAt) +{ + float pixelpos = x * vid.width / vid_conwidth.value; + int snap = (int) pixelpos; + if (pixelpos - snap >= roundUpAt) ++snap; + return ((float)snap * vid_conwidth.value / vid.width); + /* + x = (int)(x * vid.width / vid_conwidth.value); + x = (x * vid_conwidth.value / vid.width); + return x; + */ +} + +static inline float snap_to_pixel_y(float y, float roundUpAt) +{ + float pixelpos = y * vid.height / vid_conheight.value; + int snap = (int) pixelpos; + if (pixelpos - snap > roundUpAt) ++snap; + return ((float)snap * vid_conheight.value / vid.height); + /* + y = (int)(y * vid.height / vid_conheight.value); + y = (y * vid_conheight.value / vid.height); + return y; + */ +} + static void LoadFont_f(void) { dp_font_t *f; - int i; + int i, si; + float sz, sn; + const char *filelist, *c, *cm; + char mainfont[MAX_QPATH]; + if(Cmd_Argc() < 2) { Con_Printf("Available font commands:\n"); for(i = 0; i < MAX_FONTS; ++i) - Con_Printf(" loadfont %s gfx/tgafile\n", dp_fonts[i].title); + Con_Printf(" loadfont %s gfx/tgafile[...] [sizes...]\n", dp_fonts[i].title); + Con_Printf("A font can simply be gfx/tgafile, or alternatively you\n" + "can specify multiple fonts and faces\n" + "Like this: gfx/vera-sans:2,gfx/fallback:1\n" + "to load face 2 of the font gfx/vera-sans and use face 1\n" + "of gfx/fallback as fallback font.\n" + "You can also specify a list of font sizes to load, like this:\n" + "loadfont console gfx/conchars,gfx/fallback 8 12 16 24 32\n" + "In many cases, 8 12 16 24 32 should be a good choice.\n" + ); return; } f = FindFont(Cmd_Argv(1)); @@ -652,7 +708,93 @@ static void LoadFont_f(void) Con_Printf("font function not found\n"); return; } - LoadFont(true, (Cmd_Argc() < 3) ? "gfx/conchars" : Cmd_Argv(2), f); + + if(Cmd_Argc() < 3) + filelist = "gfx/conchars"; + else + filelist = Cmd_Argv(2); + + memset(f->fallbacks, 0, sizeof(f->fallbacks)); + memset(f->fallback_faces, 0, sizeof(f->fallback_faces)); + + // first font is handled "normally" + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->req_face = atoi(c+1); + else + { + f->req_face = 0; + c = cm; + } + + if(!c || (c - filelist) > MAX_QPATH) + strlcpy(mainfont, filelist, sizeof(mainfont)); + else + { + memcpy(mainfont, filelist, c - filelist); + mainfont[c - filelist] = 0; + } + + for(i = 0; i < MAX_FONT_FALLBACKS; ++i) + { + c = strchr(filelist, ','); + if(!c) + break; + filelist = c + 1; + if(!*filelist) + break; + c = strchr(filelist, ':'); + cm = strchr(filelist, ','); + if(c && (!cm || c < cm)) + f->fallback_faces[i] = atoi(c+1); + else + { + f->fallback_faces[i] = 0; // f->req_face; could make it stick to the default-font's face index + c = cm; + } + if(!c || (c-filelist) > MAX_QPATH) + { + strlcpy(f->fallbacks[i], filelist, sizeof(mainfont)); + } + else + { + memcpy(f->fallbacks[i], filelist, c - filelist); + f->fallbacks[i][c - filelist] = 0; + } + } + + // for now: by default load only one size: the default size + f->req_sizes[0] = 0; + for(i = 1; i < MAX_FONT_SIZES; ++i) + f->req_sizes[i] = -1; + + // for some reason this argc is 3 even when using 2 arguments here, maybe nexuiz screws up + if(Cmd_Argc() >= 3) + { + for(i = 0; i < Cmd_Argc()-3; ++i) + { + sz = atof(Cmd_Argv(i+3)); + if (IS_NAN(sz)) // do not use crap sizes + continue; + // now try to scale to our actual size: + if (vid.width > 0) + sn = snap_to_pixel_y(sz, 0.5); + else + { + sn = sz * vid_height.value / vid_conheight.value; + si = (int)sn; + if ( sn - (float)si >= 0.5 ) + ++si; + sn = si * vid_conheight.value / vid_height.value; + } + if (!IS_NAN(sn)) + f->req_sizes[i] = sn; + else + f->req_sizes[i] = sz; + } + } + LoadFont(true, mainfont, f); } /* @@ -668,6 +810,8 @@ static void gl_draw_start(void) numcachepics = 0; memset(cachepichash, 0, sizeof(cachepichash)); + font_start(); + for(i = 0; i < MAX_FONTS; ++i) LoadFont(false, va("gfx/font_%s", dp_fonts[i].title), &dp_fonts[i]); @@ -677,6 +821,8 @@ static void gl_draw_start(void) static void gl_draw_shutdown(void) { + font_shutdown(); + R_FreeTexturePool(&drawtexturepool); numcachepics = 0; @@ -685,6 +831,7 @@ static void gl_draw_shutdown(void) static void gl_draw_newmap(void) { + font_newmap(); } void GL_Draw_Init (void) @@ -708,6 +855,7 @@ void GL_Draw_Init (void) for(i = 0, j = 0; i < MAX_USERFONTS; ++i) if(!FONT_USER[i].title[0]) dpsnprintf(FONT_USER[i].title, sizeof(FONT_USER[i].title), "user%d", j++); + Font_Init(); } void _DrawQ_Setup(void) @@ -925,13 +1073,41 @@ static void DrawQ_GetTextColor(float color[4], int colorindex, float r, float g, } } -float DrawQ_TextWidth_Font_UntilWidth_TrackColors(const char *text, size_t *maxlen, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) +float DrawQ_TextWidth_Font_UntilWidth_TrackColors_Size(const char *text, float w, float h, size_t *maxlen, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) { - int num, colorindex = STRING_COLOR_DEFAULT; + int colorindex = STRING_COLOR_DEFAULT; size_t i; float x = 0; - char ch; + Uchar ch, mapch, nextch; + Uchar prevch = 0; // used for kerning int tempcolorindex; + float kx; + int map_index = 0; + ft2_font_map_t *fontmap = NULL; + ft2_font_map_t *map = NULL; + ft2_font_map_t *prevmap = NULL; + ft2_font_t *ft2 = fnt->ft2; + // float ftbase_x; + qboolean snap = true; + + if (!h) h = w; + if (!h) { + w = h = 1; + snap = false; + } + // do this in the end + w *= fnt->scale; + h *= fnt->scale; + + // find the most fitting size: + if (ft2 != NULL) + { + if (snap) + map_index = Font_IndexForSize(ft2, h, &w, &h); + else + map_index = Font_IndexForSize(ft2, h, NULL, NULL); + fontmap = Font_MapForIndex(ft2, map_index); + } if (*maxlen < 1) *maxlen = 1<<30; @@ -941,42 +1117,53 @@ float DrawQ_TextWidth_Font_UntilWidth_TrackColors(const char *text, size_t *maxl else colorindex = *outcolor; - maxwidth /= fnt->scale; + // maxwidth /= fnt->scale; // w and h are multiplied by it already + // ftbase_x = snap_to_pixel_x(0); - for (i = 0;i < *maxlen && text[i];i++) + for (i = 0;i < *maxlen && *text;) { - if (text[i] == ' ') + nextch = ch = u8_getchar(text, &text); + //i = text - text_start; + if (!ch) + break; + if (snap) + x = snap_to_pixel_x(x, 0.4); + if (ch == ' ' && !fontmap) { - if(x + fnt->width_of[(int) ' '] > maxwidth) + if(x + fnt->width_of[(int) ' '] * w > maxwidth) break; // oops, can't draw this - x += fnt->width_of[(int) ' ']; + x += fnt->width_of[(int) ' '] * w; + ++i; continue; } - if (text[i] == STRING_COLOR_TAG && !ignorecolorcodes && i + 1 < *maxlen) + if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i + 1 < *maxlen) { - ch = text[++i]; - if (ch <= '9' && ch >= '0') // ^[0-9] found + ++i; + ch = *text; // colors are ascii, so no u8_ needed + if (ch <= '9' && ch >= '0') // ^[0-9] found { colorindex = ch - '0'; - continue; + ++text; + ++i; + continue; } else if (ch == STRING_COLOR_RGB_TAG_CHAR && i + 3 < *maxlen ) // ^x found { // building colorindex... - ch = tolower(text[i+1]); + ch = tolower(text[1]); tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; else tempcolorindex = 0; if (tempcolorindex) { - ch = tolower(text[i+2]); + ch = tolower(text[2]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; else tempcolorindex = 0; if (tempcolorindex) { - ch = tolower(text[i+3]); + ch = tolower(text[3]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; else tempcolorindex = 0; @@ -984,20 +1171,54 @@ float DrawQ_TextWidth_Font_UntilWidth_TrackColors(const char *text, size_t *maxl { colorindex = tempcolorindex | 0xf; // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) - i+=3; + i+=4; + text += 4; continue; } } } } else if (ch == STRING_COLOR_TAG) // ^^ found, ignore the first ^ and go to print the second + { i++; + text++; + } i--; } - num = (unsigned char) text[i]; - if(x + fnt->width_of[num] > maxwidth) - break; // oops, can't draw this - x += fnt->width_of[num]; + ch = nextch; + ++i; + + if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) + { + if (ch > 0xE000) + ch -= 0xE000; + if (ch > 0xFF) + continue; + if (fontmap) + map = ft2_oldstyle_map; + prevch = 0; + if(x + fnt->width_of[ch] * w > maxwidth) + break; // oops, can't draw this + x += fnt->width_of[ch] * w; + } else { + if (!map || map == ft2_oldstyle_map || map->start < ch || map->start + FONT_CHARS_PER_MAP >= ch) + { + map = FontMap_FindForChar(fontmap, ch); + if (!map) + { + if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) + break; + if (!map) + break; + } + } + mapch = ch - map->start; + if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, NULL)) + x += kx * w; + x += map->glyphs[mapch].advance_x * w; + prevmap = map; + prevch = ch; + } } *maxlen = i; @@ -1005,12 +1226,12 @@ float DrawQ_TextWidth_Font_UntilWidth_TrackColors(const char *text, size_t *maxl if (outcolor) *outcolor = colorindex; - return x * fnt->scale; + return x; } float DrawQ_String_Font(float startx, float starty, const char *text, size_t maxlen, float w, float h, float basered, float basegreen, float baseblue, float basealpha, int flags, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt) { - int num, shadow, colorindex = STRING_COLOR_DEFAULT; + int shadow, colorindex = STRING_COLOR_DEFAULT; size_t i; float x = startx, y, s, t, u, v, thisw; float *av, *at, *ac; @@ -1019,17 +1240,47 @@ float DrawQ_String_Font(float startx, float starty, const char *text, size_t max static float vertex3f[QUADELEMENTS_MAXQUADS*4*3]; static float texcoord2f[QUADELEMENTS_MAXQUADS*4*2]; static float color4f[QUADELEMENTS_MAXQUADS*4*4]; - int ch; + Uchar ch, mapch, nextch; + Uchar prevch = 0; // used for kerning int tempcolorindex; + int map_index = 0; + ft2_font_map_t *prevmap = NULL; // the previous map + ft2_font_map_t *map = NULL; // the currently used map + ft2_font_map_t *fontmap = NULL; // the font map for the size + float ftbase_y; + const char *text_start = text; + float kx, ky; + ft2_font_t *ft2 = fnt->ft2; + qboolean snap = true; + float pix_x, pix_y; int tw, th; tw = R_TextureWidth(fnt->tex); th = R_TextureHeight(fnt->tex); + if (!h) h = w; + if (!h) { + h = w = 1; + snap = false; + } + starty -= (fnt->scale - 1) * h * 0.5; // center w *= fnt->scale; h *= fnt->scale; + if (ft2 != NULL) + { + if (snap) + map_index = Font_IndexForSize(ft2, h, &w, &h); + else + map_index = Font_IndexForSize(ft2, h, NULL, NULL); + fontmap = Font_MapForIndex(ft2, map_index); + } + + // draw the font at its baseline when using freetype + //ftbase_x = 0; + ftbase_y = h * (4.5/6.0); + if (maxlen < 1) maxlen = 1<<30; @@ -1037,6 +1288,8 @@ float DrawQ_String_Font(float startx, float starty, const char *text, size_t max R_Mesh_ColorPointer(color4f, 0, 0); R_Mesh_ResetTextureState(); + if (!fontmap) + R_Mesh_TexBind(0, R_GetTexture(fnt->tex)); R_Mesh_TexCoordPointer(0, 2, texcoord2f, 0, 0); R_Mesh_VertexPointer(vertex3f, 0, 0); R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1); @@ -1046,8 +1299,15 @@ float DrawQ_String_Font(float startx, float starty, const char *text, size_t max av = vertex3f; batchcount = 0; + //ftbase_x = snap_to_pixel_x(ftbase_x); + ftbase_y = snap_to_pixel_y(ftbase_y, 0.3); + + pix_x = vid.width / vid_conwidth.value; + pix_y = vid.height / vid_conheight.value; for (shadow = r_textshadow.value != 0 && basealpha > 0;shadow >= 0;shadow--) { + text = text_start; + if (!outcolor || *outcolor == -1) colorindex = STRING_COLOR_DEFAULT; else @@ -1057,44 +1317,59 @@ float DrawQ_String_Font(float startx, float starty, const char *text, size_t max x = startx; y = starty; + /* if (shadow) { - x += r_textshadow.value; - y += r_textshadow.value; + x += r_textshadow.value * vid.width / vid_conwidth.value; + y += r_textshadow.value * vid.height / vid_conheight.value; } - for (i = 0;i < maxlen && text[i];i++) + */ + for (i = 0;i < maxlen && *text;) { - if (text[i] == ' ') + nextch = ch = u8_getchar(text, &text); + //i = text - text_start; + if (!ch) + break; + if (snap) + { + x = snap_to_pixel_x(x, 0.4); + y = snap_to_pixel_y(y, 0.4); + } + if (ch == ' ' && !fontmap) { x += fnt->width_of[(int) ' '] * w; + ++i; continue; } - if (text[i] == STRING_COLOR_TAG && !ignorecolorcodes && i + 1 < maxlen) + if (ch == STRING_COLOR_TAG && !ignorecolorcodes && i + 1 < maxlen) { - ch = text[++i]; + ++i; + ch = *text; // colors are ascii, so no u8_ needed if (ch <= '9' && ch >= '0') // ^[0-9] found { colorindex = ch - '0'; DrawQ_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); + ++text; + ++i; continue; } else if (ch == STRING_COLOR_RGB_TAG_CHAR && i+3 < maxlen ) // ^x found { // building colorindex... - ch = tolower(text[i+1]); + ch = tolower(text[1]); tempcolorindex = 0x10000; // binary: 1,0000,0000,0000,0000 if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 12; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 12; else tempcolorindex = 0; if (tempcolorindex) { - ch = tolower(text[i+2]); + ch = tolower(text[2]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 8; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 8; else tempcolorindex = 0; if (tempcolorindex) { - ch = tolower(text[i+3]); + ch = tolower(text[3]); if (ch <= '9' && ch >= '0') tempcolorindex |= (ch - '0') << 4; else if (ch >= 'a' && ch <= 'f') tempcolorindex |= (ch - 87) << 4; else tempcolorindex = 0; @@ -1104,50 +1379,177 @@ float DrawQ_String_Font(float startx, float starty, const char *text, size_t max // ...done! now colorindex has rgba codes (1,rrrr,gggg,bbbb,aaaa) //Con_Printf("^1colorindex:^7 %x\n", colorindex); DrawQ_GetTextColor(color, colorindex, basered, basegreen, baseblue, basealpha, shadow != 0); - i+=3; + i+=4; + text+=4; continue; } } } } else if (ch == STRING_COLOR_TAG) + { i++; + text++; + } i--; } - num = (unsigned char) text[i]; - thisw = fnt->width_of[num]; - // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering - s = (num & 15)*0.0625f + (0.5f / tw); - t = (num >> 4)*0.0625f + (0.5f / th); - u = 0.0625f * thisw - (1.0f / tw); - v = 0.0625f - (1.0f / th); - ac[ 0] = color[0];ac[ 1] = color[1];ac[ 2] = color[2];ac[ 3] = color[3]; - ac[ 4] = color[0];ac[ 5] = color[1];ac[ 6] = color[2];ac[ 7] = color[3]; - ac[ 8] = color[0];ac[ 9] = color[1];ac[10] = color[2];ac[11] = color[3]; - ac[12] = color[0];ac[13] = color[1];ac[14] = color[2];ac[15] = color[3]; - at[ 0] = s ; at[ 1] = t ; - at[ 2] = s+u ; at[ 3] = t ; - at[ 4] = s+u ; at[ 5] = t+v ; - at[ 6] = s ; at[ 7] = t+v ; - av[ 0] = x ; av[ 1] = y ; av[ 2] = 10; - av[ 3] = x+w*thisw ; av[ 4] = y ; av[ 5] = 10; - av[ 6] = x+w*thisw ; av[ 7] = y+h ; av[ 8] = 10; - av[ 9] = x ; av[10] = y+h ; av[11] = 10; - ac += 16; - at += 8; - av += 12; - batchcount++; - if (batchcount >= QUADELEMENTS_MAXQUADS) + // get the backup + ch = nextch; + ++i; + // using a value of -1 for the oldstyle map because NULL means uninitialized... + // this way we don't need to rebind fnt->tex for every old-style character + // E000..E0FF: emulate old-font characters (to still have smileys and such available) + if (shadow) { - GL_LockArrays(0, batchcount * 4); - R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, quadelement3s, 0, 0); - GL_LockArrays(0, 0); - batchcount = 0; - ac = color4f; - at = texcoord2f; - av = vertex3f; + x += pix_x * r_textshadow.value; + y += pix_y * r_textshadow.value; + } + if (!fontmap || (ch <= 0xFF && fontmap->glyphs[ch].image) || (ch >= 0xE000 && ch <= 0xE0FF)) + { + if (ch > 0xE000) + ch -= 0xE000; + if (ch > 0xFF) + continue; + if (fontmap) + { + if (map != ft2_oldstyle_map) + { + if (batchcount) + { + // switching from freetype to non-freetype rendering + GL_LockArrays(0, batchcount * 4); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, quadelement3s, 0, 0); + GL_LockArrays(0, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + R_SetupShader_Generic(fnt->tex, NULL, GL_MODULATE, 1); + map = ft2_oldstyle_map; + } + } + prevch = 0; + //num = (unsigned char) text[i]; + //thisw = fnt->width_of[num]; + thisw = fnt->width_of[ch]; + // FIXME make these smaller to just include the occupied part of the character for slightly faster rendering + s = (ch & 15)*0.0625f + (0.5f / tw); + t = (ch >> 4)*0.0625f + (0.5f / th); + u = 0.0625f * thisw - (1.0f / tw); + v = 0.0625f - (1.0f / th); + ac[ 0] = color[0];ac[ 1] = color[1];ac[ 2] = color[2];ac[ 3] = color[3]; + ac[ 4] = color[0];ac[ 5] = color[1];ac[ 6] = color[2];ac[ 7] = color[3]; + ac[ 8] = color[0];ac[ 9] = color[1];ac[10] = color[2];ac[11] = color[3]; + ac[12] = color[0];ac[13] = color[1];ac[14] = color[2];ac[15] = color[3]; + at[ 0] = s ; at[ 1] = t ; + at[ 2] = s+u ; at[ 3] = t ; + at[ 4] = s+u ; at[ 5] = t+v ; + at[ 6] = s ; at[ 7] = t+v ; + av[ 0] = x ; av[ 1] = y ; av[ 2] = 10; + av[ 3] = x+w*thisw ; av[ 4] = y ; av[ 5] = 10; + av[ 6] = x+w*thisw ; av[ 7] = y+h ; av[ 8] = 10; + av[ 9] = x ; av[10] = y+h ; av[11] = 10; + ac += 16; + at += 8; + av += 12; + batchcount++; + if (batchcount >= QUADELEMENTS_MAXQUADS) + { + GL_LockArrays(0, batchcount * 4); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, quadelement3s, 0, 0); + GL_LockArrays(0, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + x += thisw * w; + } else { + if (!map || map == ft2_oldstyle_map || map->start < ch || map->start + FONT_CHARS_PER_MAP >= ch) + { + // new charmap - need to render + if (batchcount) + { + // we need a different character map, render what we currently have: + GL_LockArrays(0, batchcount * 4); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, quadelement3s, 0, 0); + GL_LockArrays(0, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + // find the new map + map = FontMap_FindForChar(fontmap, ch); + if (!map) + { + if (!Font_LoadMapForIndex(ft2, map_index, ch, &map)) + { + shadow = -1; + break; + } + if (!map) + { + // this shouldn't happen + shadow = -1; + break; + } + } + R_SetupShader_Generic(map->texture, NULL, GL_MODULATE, 1); + } + + mapch = ch - map->start; + thisw = map->glyphs[mapch].advance_x; + + //x += ftbase_x; + y += ftbase_y; + if (prevch && Font_GetKerningForMap(ft2, map_index, w, h, prevch, ch, &kx, &ky)) + { + x += kx * w; + y += ky * h; + } + else + kx = ky = 0; + ac[ 0] = color[0]; ac[ 1] = color[1]; ac[ 2] = color[2]; ac[ 3] = color[3]; + ac[ 4] = color[0]; ac[ 5] = color[1]; ac[ 6] = color[2]; ac[ 7] = color[3]; + ac[ 8] = color[0]; ac[ 9] = color[1]; ac[10] = color[2]; ac[11] = color[3]; + ac[12] = color[0]; ac[13] = color[1]; ac[14] = color[2]; ac[15] = color[3]; + at[0] = map->glyphs[mapch].txmin; at[1] = map->glyphs[mapch].tymin; + at[2] = map->glyphs[mapch].txmax; at[3] = map->glyphs[mapch].tymin; + at[4] = map->glyphs[mapch].txmax; at[5] = map->glyphs[mapch].tymax; + at[6] = map->glyphs[mapch].txmin; at[7] = map->glyphs[mapch].tymax; + av[ 0] = x + w * map->glyphs[mapch].vxmin; av[ 1] = y + h * map->glyphs[mapch].vymin; av[ 2] = 10; + av[ 3] = x + w * map->glyphs[mapch].vxmax; av[ 4] = y + h * map->glyphs[mapch].vymin; av[ 5] = 10; + av[ 6] = x + w * map->glyphs[mapch].vxmax; av[ 7] = y + h * map->glyphs[mapch].vymax; av[ 8] = 10; + av[ 9] = x + w * map->glyphs[mapch].vxmin; av[10] = y + h * map->glyphs[mapch].vymax; av[11] = 10; + //x -= ftbase_x; + y -= ftbase_y; + + x += thisw * w; + ac += 16; + at += 8; + av += 12; + batchcount++; + if (batchcount >= QUADELEMENTS_MAXQUADS) + { + GL_LockArrays(0, batchcount * 4); + R_Mesh_Draw(0, batchcount * 4, 0, batchcount * 2, quadelement3i, quadelement3s, 0, 0); + GL_LockArrays(0, 0); + batchcount = 0; + ac = color4f; + at = texcoord2f; + av = vertex3f; + } + + prevmap = map; + prevch = ch; + } + if (shadow) + { + x -= pix_x * r_textshadow.value; + y -= pix_y * r_textshadow.value; } - x += thisw * w; } } if (batchcount > 0) @@ -1174,11 +1576,26 @@ float DrawQ_TextWidth_Font(const char *text, size_t maxlen, qboolean ignorecolor return DrawQ_TextWidth_Font_UntilWidth(text, &maxlen, ignorecolorcodes, fnt, 1000000000); } +float DrawQ_TextWidth_Font_Size(const char *text, float w, float h, size_t maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt) +{ + return DrawQ_TextWidth_Font_UntilWidth_Size(text, w, h, &maxlen, ignorecolorcodes, fnt, 1000000000); +} + float DrawQ_TextWidth_Font_UntilWidth(const char *text, size_t *maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth) { return DrawQ_TextWidth_Font_UntilWidth_TrackColors(text, maxlen, NULL, ignorecolorcodes, fnt, maxWidth); } +float DrawQ_TextWidth_Font_UntilWidth_Size(const char *text, float w, float h, size_t *maxlen, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxWidth) +{ + return DrawQ_TextWidth_Font_UntilWidth_TrackColors_Size(text, w, h, maxlen, NULL, ignorecolorcodes, fnt, maxWidth); +} + +float DrawQ_TextWidth_Font_UntilWidth_TrackColors(const char *text, size_t *maxlen, int *outcolor, qboolean ignorecolorcodes, const dp_font_t *fnt, float maxwidth) +{ + return DrawQ_TextWidth_Font_UntilWidth_TrackColors_Size(text, 0, 0, maxlen, outcolor, ignorecolorcodes, fnt, maxwidth); +} + #if 0 // not used // no ^xrgb management diff --git a/gl_rmain.c b/gl_rmain.c index d45a0eef..0e3266f2 100644 --- a/gl_rmain.c +++ b/gl_rmain.c @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "r_shadow.h" #include "polygon.h" #include "image.h" +#include "ft2.h" mempool_t *r_main_mempool; rtexturepool_t *r_main_texturepool; @@ -4191,12 +4192,14 @@ extern void gl_backend_init(void); extern void Sbar_Init(void); extern void R_LightningBeams_Init(void); extern void Mod_RenderInit(void); +extern void Font_Init(void); void Render_Init(void) { gl_backend_init(); R_Textures_Init(); GL_Main_Init(); + Font_Init(); GL_Draw_Init(); R_Shadow_Init(); R_Sky_Init(); diff --git a/gl_textures.c b/gl_textures.c index 25074722..249ced51 100644 --- a/gl_textures.c +++ b/gl_textures.c @@ -63,6 +63,8 @@ static textypeinfo_t textype_bgra_compress = {TEXTYPE_BGRA , 4, 4, 0. static textypeinfo_t textype_bgra_alpha_compress = {TEXTYPE_BGRA , 4, 4, 1.0f, GL_BGRA , GL_COMPRESSED_RGBA_ARB, GL_UNSIGNED_BYTE}; static textypeinfo_t textype_shadowmap16 = {TEXTYPE_SHADOWMAP,2,2, 2.0f, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16_ARB, GL_UNSIGNED_SHORT}; static textypeinfo_t textype_shadowmap24 = {TEXTYPE_SHADOWMAP,4,4, 4.0f, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT24_ARB, GL_UNSIGNED_INT}; +static textypeinfo_t textype_alpha = {TEXTYPE_ALPHA , 1, 4, 4.0f, GL_ALPHA , 4, GL_UNSIGNED_BYTE}; +static textypeinfo_t textype_alpha_compress = {TEXTYPE_ALPHA , 1, 4, 1.0f, GL_ALPHA , GL_COMPRESSED_RGBA_ARB, GL_UNSIGNED_BYTE}; typedef enum gltexturetype_e { @@ -173,6 +175,8 @@ static textypeinfo_t *R_GetTexTypeInfo(textype_t textype, int flags) return &textype_rgba_alpha_compress; case TEXTYPE_BGRA: return &textype_bgra_alpha_compress; + case TEXTYPE_ALPHA: + return &textype_alpha_compress; default: Host_Error("R_GetTexTypeInfo: unknown texture format"); return NULL; @@ -188,6 +192,8 @@ static textypeinfo_t *R_GetTexTypeInfo(textype_t textype, int flags) return &textype_rgba_compress; case TEXTYPE_BGRA: return &textype_bgra_compress; + case TEXTYPE_ALPHA: + return &textype_alpha_compress; default: Host_Error("R_GetTexTypeInfo: unknown texture format"); return NULL; @@ -206,6 +212,8 @@ static textypeinfo_t *R_GetTexTypeInfo(textype_t textype, int flags) return &textype_rgba_alpha; case TEXTYPE_BGRA: return &textype_bgra_alpha; + case TEXTYPE_ALPHA: + return &textype_alpha; default: Host_Error("R_GetTexTypeInfo: unknown texture format"); return NULL; @@ -223,6 +231,8 @@ static textypeinfo_t *R_GetTexTypeInfo(textype_t textype, int flags) return &textype_bgra; case TEXTYPE_SHADOWMAP: return (flags & TEXF_LOWPRECISION) ? &textype_shadowmap16 : &textype_shadowmap24; + case TEXTYPE_ALPHA: + return &textype_alpha; default: Host_Error("R_GetTexTypeInfo: unknown texture format"); return NULL; @@ -1029,6 +1039,9 @@ static rtexture_t *R_SetupTexture(rtexturepool_t *rtexturepool, const char *iden break; case TEXTYPE_SHADOWMAP: break; + case TEXTYPE_ALPHA: + flags |= TEXF_ALPHA; + break; default: Host_Error("R_LoadTexture: unknown texture type"); } diff --git a/glquake.h b/glquake.h index d657de44..b8a22432 100644 --- a/glquake.h +++ b/glquake.h @@ -204,6 +204,7 @@ typedef ptrdiff_t GLsizeiptrARB; #define GL_OUT_OF_MEMORY 0x0505 #define GL_DITHER 0x0BD0 +#define GL_ALPHA 0x1906 #define GL_RGB 0x1907 #define GL_RGBA 0x1908 diff --git a/host.c b/host.c index 549f0dfa..67ab1b57 100644 --- a/host.c +++ b/host.c @@ -975,6 +975,7 @@ char engineversion[128]; qboolean sys_nostdout = false; +extern void u8_Init(void); extern void Render_Init(void); extern void Mathlib_Init(void); extern void FS_Init(void); @@ -1042,6 +1043,7 @@ static void Host_Init (void) Con_Init(); // initialize various cvars that could not be initialized earlier + u8_Init(); Curl_Init_Commands(); Cmd_Init_Commands(); Sys_Init_Commands(); diff --git a/host_cmd.c b/host_cmd.c index ece15660..80bc0a7f 100644 --- a/host_cmd.c +++ b/host_cmd.c @@ -22,6 +22,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "sv_demo.h" #include "image.h" +#include "utf8lib.h" + // for secure rcon authentication #include "hmac.h" #include "mdfour.h" @@ -1027,7 +1029,7 @@ void Host_Name_f (void) host_client->name[1] = '0' + STRING_COLOR_DEFAULT; } - COM_StringLengthNoColors(host_client->name, 0, &valid_colors); + u8_COM_StringLengthNoColors(host_client->name, 0, &valid_colors); if(!valid_colors) // NOTE: this also proves the string is not empty, as "" is a valid colored string { size_t l; diff --git a/keys.c b/keys.c index a2878c0c..8c4aeff8 100644 --- a/keys.c +++ b/keys.c @@ -22,6 +22,7 @@ #include "quakedef.h" #include "cl_video.h" +#include "utf8lib.h" cvar_t con_closeontoggleconsole = {CVAR_SAVE, "con_closeontoggleconsole","1", "allows toggleconsole binds to close the console as well"}; @@ -475,7 +476,7 @@ Interactive line editing and console scrollback ==================== */ static void -Key_Console (int key, int ascii) +Key_Console (int key, int unicode) { // LordHavoc: copied most of this from Q2 to improve keyboard handling switch (key) @@ -552,6 +553,7 @@ Key_Console (int key, int ascii) if (i > 0) { // terencehill: insert the clipboard text between the characters of the line + /* char *temp = (char *) Z_Malloc(MAX_INPUTLINE); cbd[i]=0; temp[0]=0; @@ -563,6 +565,12 @@ Key_Console (int key, int ascii) strlcat(key_line, temp, sizeof(key_line)); Z_Free(temp); key_linepos += i; + */ + // blub: I'm changing this to use memmove() like the rest of the code does. + cbd[i] = 0; + memmove(key_line + key_linepos + i, key_line + key_linepos, sizeof(key_line) - key_linepos - i); + memcpy(key_line + key_linepos, cbd, i); + key_linepos += i; } Z_Free(cbd); } @@ -704,7 +712,8 @@ Key_Console (int key, int ascii) else if(keydown[K_SHIFT]) // move cursor to the previous character ignoring colors { int pos; - pos = key_linepos-1; + size_t inchar; + pos = u8_prevbyte(key_line, key_linepos); while (pos) if(pos-1 > 0 && key_line[pos-1] == STRING_COLOR_TAG && isdigit(key_line[pos])) pos-=2; @@ -718,10 +727,14 @@ Key_Console (int key, int ascii) pos--; break; } - key_linepos = pos + 1; + // we need to move to the beginning of the character when in a wide character: + u8_charidx(key_line, pos + 1, &inchar); + key_linepos = pos + 1 - inchar; } else - key_linepos--; + { + key_linepos = u8_prevbyte(key_line, key_linepos); + } return; } @@ -730,8 +743,9 @@ Key_Console (int key, int ascii) { if (key_linepos > 1) { - strlcpy(key_line + key_linepos - 1, key_line + key_linepos, sizeof(key_line) + 1 - key_linepos); - key_linepos--; + int newpos = u8_prevbyte(key_line, key_linepos); + strlcpy(key_line + newpos, key_line + key_linepos, sizeof(key_line) + 1 - key_linepos); + key_linepos = newpos; } return; } @@ -742,7 +756,7 @@ Key_Console (int key, int ascii) size_t linelen; linelen = strlen(key_line); if (key_linepos < (int)linelen) - memmove(key_line + key_linepos, key_line + key_linepos + 1, linelen - key_linepos); + memmove(key_line + key_linepos, key_line + key_linepos + u8_bytelen(key_line + key_linepos, 1), linelen - key_linepos); return; } @@ -796,7 +810,7 @@ Key_Console (int key, int ascii) // skip the char if (key_line[pos] == STRING_COLOR_TAG && key_line[pos+1] == STRING_COLOR_TAG) // consider ^^ as a character pos++; - pos++; + pos += u8_bytelen(key_line + pos, 1); // now go beyond all next consecutive color tags, if any if(pos < len) @@ -812,7 +826,7 @@ Key_Console (int key, int ascii) key_linepos = pos; } else - key_linepos++; + key_linepos += u8_bytelen(key_line + key_linepos, 1); return; } @@ -923,22 +937,31 @@ Key_Console (int key, int ascii) } // non printable - if (ascii < 32) + if (unicode < 32) return; if (key_linepos < MAX_INPUTLINE-1) { + char buf[16]; int len; + int blen; + blen = u8_fromchar(unicode, buf, sizeof(buf)); + if (!blen) + return; len = (int)strlen(&key_line[key_linepos]); // check insert mode, or always insert if at end of line if (key_insert || len == 0) { // can't use strcpy to move string to right len++; - memmove(&key_line[key_linepos + 1], &key_line[key_linepos], len); + //memmove(&key_line[key_linepos + u8_bytelen(key_line + key_linepos, 1)], &key_line[key_linepos], len); + memmove(&key_line[key_linepos + blen], &key_line[key_linepos], len); } - key_line[key_linepos] = ascii; - key_linepos++; + memcpy(key_line + key_linepos, buf, blen); + key_linepos += blen; + //key_linepos += u8_fromchar(unicode, key_line + key_linepos, sizeof(key_line) - key_linepos - 1); + //key_line[key_linepos] = ascii; + //key_linepos++; } } @@ -953,7 +976,6 @@ extern int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos); static void Key_Message (int key, int ascii) { - if (key == K_ENTER || ascii == 10 || ascii == 13) { if(chat_mode < 0) @@ -978,7 +1000,7 @@ Key_Message (int key, int ascii) if (key == K_BACKSPACE) { if (chat_bufferlen) { - chat_bufferlen--; + chat_bufferlen = u8_prevbyte(chat_buffer, chat_bufferlen); chat_buffer[chat_bufferlen] = 0; } return; @@ -995,8 +1017,10 @@ Key_Message (int key, int ascii) if (!ascii) return; // non printable - chat_buffer[chat_bufferlen++] = ascii; - chat_buffer[chat_bufferlen] = 0; + chat_bufferlen += u8_fromchar(ascii, chat_buffer+chat_bufferlen, sizeof(chat_buffer) - chat_bufferlen - 1); + + //chat_buffer[chat_bufferlen++] = ascii; + //chat_buffer[chat_bufferlen] = 0; } //============================================================================ diff --git a/makefile.inc b/makefile.inc index 4d114900..30e41b3c 100644 --- a/makefile.inc +++ b/makefile.inc @@ -111,6 +111,8 @@ OBJ_COMMON= \ filematch.o \ fractalnoise.o \ fs.o \ + ft2.o \ + utf8lib.o \ gl_backend.o \ gl_draw.o \ gl_rmain.o \ diff --git a/prvm_cmds.c b/prvm_cmds.c index 23133207..e3e1b322 100644 --- a/prvm_cmds.c +++ b/prvm_cmds.c @@ -10,6 +10,8 @@ #include "libcurl.h" #include +#include "ft2.h" + extern cvar_t prvm_backtraceforwarnings; // LordHavoc: changed this to NOT use a return statement, so that it can be used in functions that must return a value @@ -2167,7 +2169,8 @@ void VM_strlen(void) { VM_SAFEPARMCOUNT(1,VM_strlen); - PRVM_G_FLOAT(OFS_RETURN) = strlen(PRVM_G_STRING(OFS_PARM0)); + //PRVM_G_FLOAT(OFS_RETURN) = strlen(PRVM_G_STRING(OFS_PARM0)); + PRVM_G_FLOAT(OFS_RETURN) = u8_strlen(PRVM_G_STRING(OFS_PARM0)); } // DRESK - Decolorized String @@ -2210,7 +2213,8 @@ void VM_strlennocol(void) szString = PRVM_G_STRING(OFS_PARM0); - nCnt = COM_StringLengthNoColors(szString, 0, NULL); + //nCnt = COM_StringLengthNoColors(szString, 0, NULL); + nCnt = u8_COM_StringLengthNoColors(szString, 0, NULL); PRVM_G_FLOAT(OFS_RETURN) = nCnt; } @@ -2290,12 +2294,15 @@ string substring(string s, float start, float length) // returns a section of a string as a tempstring void VM_substring(void) { - int start, length, slength, maxlen; + int start, length; + int u_slength = 0, u_start; + size_t u_length; const char *s; char string[VM_STRINGTEMP_LENGTH]; VM_SAFEPARMCOUNT(3,VM_substring); + /* s = PRVM_G_STRING(OFS_PARM0); start = (int)PRVM_G_FLOAT(OFS_PARM1); length = (int)PRVM_G_FLOAT(OFS_PARM2); @@ -2313,6 +2320,40 @@ void VM_substring(void) memcpy(string, s + start, length); string[length] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); + */ + + s = PRVM_G_STRING(OFS_PARM0); + start = (int)PRVM_G_FLOAT(OFS_PARM1); + length = (int)PRVM_G_FLOAT(OFS_PARM2); + + if (start < 0) // FTE_STRINGS feature + { + u_slength = u8_strlen(s); + start += u_slength; + start = bound(0, start, u_slength); + } + + if (length < 0) // FTE_STRINGS feature + { + if (!u_slength) // it's not calculated when it's not needed above + u_slength = u8_strlen(s); + length += u_slength - start + 1; + } + + // positive start, positive length + u_start = u8_byteofs(s, start, NULL); + if (u_start < 0) + { + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(""); + return; + } + u_length = u8_bytelen(s + u_start, length); + if (u_length >= sizeof(string)-1) + u_length = sizeof(string)-1; + + memcpy(string, s + u_start, u_length); + string[u_length] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string); } /* @@ -3095,6 +3136,7 @@ string chr(float ascii) */ void VM_chr(void) { + /* char tmp[2]; VM_SAFEPARMCOUNT(1, VM_chr); @@ -3102,6 +3144,17 @@ void VM_chr(void) tmp[1] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(tmp); + */ + + char tmp[8]; + int len; + VM_SAFEPARMCOUNT(1, VM_chr); + + len = u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0), tmp, sizeof(tmp)); + if (len < 0) + len = 0; + tmp[len] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(tmp); } //============================================================================= @@ -3262,6 +3315,7 @@ void VM_drawstring(void) Con_Printf("VM_drawstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale"))); DrawQ_String_Font(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont()); + //Font_DrawString(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true); PRVM_G_FLOAT(OFS_RETURN) = 1; } @@ -3315,10 +3369,30 @@ float stringwidth(string text, float allowColorCodes, float size) void VM_stringwidth(void) { const char *string; - float sz, mult; // sz is intended font size so we can later add freetype support, mult is font size multiplier in pixels per character cell + float *szv; + float mult; // sz is intended font size so we can later add freetype support, mult is font size multiplier in pixels per character cell int colors; + float x[200]; VM_SAFEPARMCOUNTRANGE(2,3,VM_drawstring); + if(prog->argc == 3) + { + szv = PRVM_G_VECTOR(OFS_PARM2); + mult = 1; + } + else + { + static float defsize[] = {0, 0}; + szv = defsize; + mult = 1; + } + x[180] = 3; + + string = PRVM_G_STRING(OFS_PARM0); + colors = (int)PRVM_G_FLOAT(OFS_PARM1); + + PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth_Font_Size(string, szv[0], szv[1], 0, !colors, getdrawfont()) * mult; // 1x1 characters, don't actually draw +/* if(prog->argc == 3) { mult = sz = PRVM_G_FLOAT(OFS_PARM2); @@ -3333,6 +3407,8 @@ void VM_stringwidth(void) colors = (int)PRVM_G_FLOAT(OFS_PARM1); PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth_Font(string, 0, !colors, getdrawfont()) * mult; // 1x1 characters, don't actually draw +*/ + } /* ========= @@ -4929,6 +5005,7 @@ void VM_strstrofs (void) instr = PRVM_G_STRING(OFS_PARM0); match = PRVM_G_STRING(OFS_PARM1); firstofs = (prog->argc > 2)?(int)PRVM_G_FLOAT(OFS_PARM2):0; + firstofs = u8_bytelen(instr, firstofs); if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr))) { @@ -4947,10 +5024,17 @@ void VM_strstrofs (void) void VM_str2chr (void) { const char *s; + Uchar ch; + int index; VM_SAFEPARMCOUNT(2, VM_str2chr); s = PRVM_G_STRING(OFS_PARM0); - if((unsigned)PRVM_G_FLOAT(OFS_PARM1) < strlen(s)) - PRVM_G_FLOAT(OFS_RETURN) = (unsigned char)s[(unsigned)PRVM_G_FLOAT(OFS_PARM1)]; + index = u8_bytelen(s, (int)PRVM_G_FLOAT(OFS_PARM1)); + + if((unsigned)index < strlen(s)) + { + ch = u8_getchar(s + index, NULL); + PRVM_G_FLOAT(OFS_RETURN) = ch; + } else PRVM_G_FLOAT(OFS_RETURN) = 0; } @@ -4958,6 +5042,7 @@ void VM_str2chr (void) //#223 string(float c, ...) chr2str (FTE_STRINGS) void VM_chr2str (void) { + /* char t[9]; int i; VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); @@ -4965,6 +5050,19 @@ void VM_chr2str (void) t[i] = (unsigned char)PRVM_G_FLOAT(OFS_PARM0+i*3); t[i] = 0; PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t); + */ + char t[9 * 4 + 1]; + int i; + size_t len = 0; + VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str); + for(i = 0; i < prog->argc && len < sizeof(t)-1; ++i) + { + int add = u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0+i*3), t + len, sizeof(t)-1); + if(add > 0) + len += add; + } + t[len] = 0; + PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t); } static int chrconv_number(int i, int base, int conv) diff --git a/r_textures.h b/r_textures.h index 12b5cc5e..503163f8 100644 --- a/r_textures.h +++ b/r_textures.h @@ -39,6 +39,8 @@ typedef enum textype_e TEXTYPE_BGRA, // 16bit D16 (16bit depth) or 32bit S8D24 (24bit depth, 8bit stencil unused) TEXTYPE_SHADOWMAP, + // 8bit ALPHA (used for freetype fonts) + TEXTYPE_ALPHA, } textype_t; diff --git a/vid_sdl.c b/vid_sdl.c index 7a3076a3..a00029bf 100644 --- a/vid_sdl.c +++ b/vid_sdl.c @@ -388,7 +388,7 @@ void Sys_SendKeyEvents( void ) break; case SDL_KEYDOWN: case SDL_KEYUP: - Key_Event( MapKey( event.key.keysym.sym ), (char)event.key.keysym.unicode, (event.key.state == SDL_PRESSED) ); + Key_Event( MapKey( event.key.keysym.sym ), event.key.keysym.unicode, (event.key.state == SDL_PRESSED) ); break; case SDL_ACTIVEEVENT: if( event.active.state & SDL_APPACTIVE )