X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;ds=sidebyside;f=qcsrc%2Flib%2Fstring.qh;h=c99497bc7566c6cd1d6bdf381dc3e1175387b815;hb=4e21f418ad9e6287efb942c1fa2861a51981110a;hp=33aacebabdbd8d8eee975006f6c9c1bf4dab1c5d;hpb=264c9f48d418d1ebd4b5ef5868b85f92644917dc;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/lib/string.qh b/qcsrc/lib/string.qh index 33aacebab..c99497bc7 100644 --- a/qcsrc/lib/string.qh +++ b/qcsrc/lib/string.qh @@ -4,6 +4,25 @@ #include "sort.qh" #include "oo.qh" +// string logic +// +// true: is truthy +// == "": is equal to "" +// is "": has the same string index as the string constant "" +// strunzone: can be strunzoned +// +// | | true | == "" | is "" | strunzone | +// | :----------: | :--: | :---: | :---: | :-------: | +// | nil | | yes | | | +// | strcat(nil) | yes | yes | | | +// | strzone(nil) | yes | yes | | yes | +// | "" | yes | yes | yes | | +// | strcat("") | yes | yes | | | +// | strzone("") | yes | yes | | yes | +// | "s" | yes | | | | +// | strcat("s") | yes | | | | +// | strzone("s") | yes | | | yes | + #ifdef CSQC float stringwidth_colors(string s, vector theSize) { @@ -27,7 +46,78 @@ } #endif -[[eraseable]] +#define strcpy(this, s) MACRO_BEGIN \ + if (this) { \ + strunzone(this); \ + } \ + this = strzone(s); \ +MACRO_END + +#define strfree(this) MACRO_BEGIN \ + if (this) { \ + strunzone(this); \ + } \ + this = string_null; \ +MACRO_END + +// Returns the number of days since 0000-03-01 (March 1, year 0) +// Starting counting from March, as the 1st month of the year, February becomes the 12th and last month, +// so its variable duration does not affect, given that the 29th is the last day of the period +ERASEABLE +int days_up_to_date(int Y, int M, int D) +{ + int years = (M <= 2) ? Y - 1 : Y; + + int leap_days = floor(years / 4) - floor(years / 100) + floor(years / 400); + + // using these 2 formulas to save 2 arrays or switches (performance isn't important here) + int months = (M <= 2) ? (M + 9) : (M - 3); // 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + int leftover_days = (M <= 2) ? (M + 5) : floor(0.58 * M - 1.1); // 6, 7, 0, 1, 1, 2, 2, 3, 4, 4, 5, 5 + + int month_days = 30 * months + leftover_days; + + return 365 * years + month_days + D + leap_days; +} + +#define DAYS_UP_TO_EPOCH 719469 // days_up_to_date(1970, 1, 1); + +// Returns the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC). +// This function exists only as a replacement for strftime(false, "%s") which doesn't work +// on Windows (%s is not supported) and at least in some linux systems doesn't return the +// correct result +// NOTE: at the current date, the number (string) returned by both strftime(false, "%s") and +// strftime_s() is so high that can't be converted to int (with ftos) without precision loss +ERASEABLE +string strftime_s() +{ + string date = strftime(false, "%Y-%m-%d %H:%M:%S"); + int i, seconds = 0; + i =0; int Y = stof(substring(date, i, 4)); // years + i+=5; int M = stof(substring(date, i, 2)); // months + i+=3; int D = stof(substring(date, i, 2)); // days + + i+=3; seconds += stof(substring(date, i, 2)) * 60 * 60; // hours + i+=3; seconds += stof(substring(date, i, 2)) * 60; // minutes + i+=3; seconds += stof(substring(date, i, 2)); // seconds + + // doing so we loose precision + //seconds += (days_up_to_date(Y, M, D) - DAYS_UP_TO_EPOCH) * 24 * 60 * 60; + //return ftos(seconds); + + int days_since_epoch = days_up_to_date(Y, M, D) - DAYS_UP_TO_EPOCH; + // use hundreds of seconds as unit to avoid precision loss + int hundreds_of_seconds = days_since_epoch * 24 * 6 * 6; + hundreds_of_seconds += floor(seconds / 100); + + // tens of seconds and seconds + string seconds_str = ftos(seconds % 100); + if ((seconds % 100) < 10) + seconds_str = strcat("0", seconds_str); + + return strcat(ftos(hundreds_of_seconds), seconds_str); +} + +ERASEABLE string seconds_tostring(float sec) { float minutes = floor(sec / 60); @@ -35,7 +125,7 @@ string seconds_tostring(float sec) return sprintf("%d:%02d", minutes, sec); } -[[eraseable]] +ERASEABLE string format_time(float seconds) { seconds = floor(seconds + 0.5); @@ -49,7 +139,7 @@ string format_time(float seconds) else return sprintf(_("%02d:%02d:%02d"), hours, minutes, seconds); } -[[eraseable]] +ERASEABLE string mmsss(float tenths) { tenths = floor(tenths + 0.5); @@ -59,7 +149,7 @@ string mmsss(float tenths) return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1)); } -[[eraseable]] +ERASEABLE string mmssss(float hundredths) { hundredths = floor(hundredths + 0.5); @@ -71,7 +161,7 @@ string mmssss(float hundredths) int ColorTranslateMode; -[[eraseable]] +ERASEABLE string ColorTranslateRGB(string s) { return (ColorTranslateMode & 1) ? strdecolorize(s) : s; @@ -94,8 +184,6 @@ string autocvar_hud_colorset_background = "7"; // BG - White // neutral/unimpo /** color code replace, place inside of sprintf and parse the string */ string CCR(string input) { - // See the autocvar declarations in util.qh for default values - // foreground/normal colors input = strreplace("^F1", strcat("^", autocvar_hud_colorset_foreground_1), input); input = strreplace("^F2", strcat("^", autocvar_hud_colorset_foreground_2), input); @@ -116,7 +204,7 @@ string CCR(string input) #define startsWith(haystack, needle) (strstrofs(haystack, needle, 0) == 0) -[[eraseable]] +ERASEABLE bool startsWithNocase(string haystack, string needle) { return strcasecmp(substring(haystack, 0, strlen(needle)), needle) == 0; @@ -126,7 +214,7 @@ noref string _endsWith_suffix; #define endsWith(this, suffix) (_endsWith_suffix = suffix, substring(this, -strlen(_endsWith_suffix), -1) == _endsWith_suffix) /** unzone the string, and return it as tempstring. Safe to be called on string_null */ -[[eraseable]] +ERASEABLE string fstrunzone(string s) { if (!s) return s; @@ -136,7 +224,7 @@ string fstrunzone(string s) } /** returns first word */ -[[eraseable]] +ERASEABLE string car(string s) { int o = strstrofs(s, " ", 0); @@ -145,7 +233,7 @@ string car(string s) } /** returns all but first word */ -[[eraseable]] +ERASEABLE string cdr(string s) { int o = strstrofs(s, " ", 0); @@ -153,7 +241,7 @@ string cdr(string s) return substring(s, o + 1, strlen(s) - (o + 1)); } -[[eraseable]] +ERASEABLE string cons(string a, string b) { if (a == "") return b; @@ -161,13 +249,21 @@ string cons(string a, string b) return strcat(a, " ", b); } -[[eraseable]] +ERASEABLE +string cons_mid(string a, string mid, string b) +{ + if (a == "") return b; + if (b == "") return a; + return strcat(a, mid, b); +} + +ERASEABLE string substring_range(string s, float b, float e) { return substring(s, b, e - b); } -[[eraseable]] +ERASEABLE string swapwords(string str, float i, float j) { float n; @@ -189,13 +285,13 @@ string swapwords(string str, float i, float j) } string _shufflewords_str; -[[eraseable]] +ERASEABLE void _shufflewords_swapfunc(float i, float j, entity pass) { _shufflewords_str = swapwords(_shufflewords_str, i, j); } -[[eraseable]] +ERASEABLE string shufflewords(string str) { _shufflewords_str = str; @@ -206,7 +302,7 @@ string shufflewords(string str) return str; } -[[eraseable]] +ERASEABLE string unescape(string in) { in = strzone(in); // but it doesn't seem to be necessary in my tests at least @@ -231,7 +327,7 @@ string unescape(string in) return str; } -[[eraseable]] +ERASEABLE string strwords(string s, int w) { int endpos = 0; @@ -243,7 +339,7 @@ string strwords(string s, int w) #define strhasword(s, w) (strstrofs(strcat(" ", s, " "), strcat(" ", w, " "), 0) >= 0) -[[eraseable]] +ERASEABLE int u8_strsize(string s) { int l = 0; @@ -256,7 +352,8 @@ int u8_strsize(string s) return l; } -[[eraseable]] +// List of Unicode spaces: http://jkorpela.fi/chars/spaces.html +ERASEABLE bool isInvisibleString(string s) { s = strdecolorize(s); @@ -272,8 +369,29 @@ bool isInvisibleString(string s) case 192: // charmap space if (!utf8) break; return false; - case 160: // space in unicode fonts - case 0xE000 + 192: // utf8 charmap space + case 0xE000: // invisible char of the utf8 quake charmap + case 0xE00A: // invisible char of the utf8 quake charmap + case 0xE0A0: // invisible char of the utf8 quake charmap + case 0xE020: // invisible char of the utf8 quake charmap + case 0x00A0: // NO-BREAK SPACE + //case 0x1680: // OGHAM SPACE MARK + case 0x180E: // MONGOLIAN VOWEL SEPARATOR + case 0x2000: // EN QUAD + case 0x2001: // EM QUAD + case 0x2002: // EN SPACE + case 0x2003: // EM SPACE + case 0x2004: // THREE-PER-EM SPACE + case 0x2005: // FOUR-PER-EM SPACE + case 0x2006: // SIX-PER-EM SPACE + case 0x2007: // FIGURE SPACE + case 0x2008: // PUNCTUATION SPACE + case 0x2009: // THIN SPACE + case 0x200A: // HAIR SPACE + case 0x200B: // ZERO WIDTH SPACE + case 0x202F: // NARROW NO-BREAK SPACE + case 0x205F: // MEDIUM MATHEMATICAL SPACE + case 0x3000: // IDEOGRAPHIC SPACE + case 0xFEFF: // ZERO WIDTH NO-BREAK SPACE if (utf8) break; default: return false; @@ -284,7 +402,7 @@ bool isInvisibleString(string s) // Multiline text file buffers -[[eraseable]] +ERASEABLE int buf_load(string pFilename) { int buf = buf_create(); @@ -302,7 +420,7 @@ int buf_load(string pFilename) return buf; } -[[eraseable]] +ERASEABLE void buf_save(float buf, string pFilename) { int fh = fopen(pFilename, FILE_WRITE); @@ -316,7 +434,7 @@ void buf_save(float buf, string pFilename) /** * converts a number to a string with the indicated number of decimals */ -[[eraseable]] +ERASEABLE string ftos_decimals(float number, int decimals) { // inhibit stupid negative zero @@ -327,7 +445,7 @@ string ftos_decimals(float number, int decimals) /** * converts a number to a string with the minimum number of decimals */ -[[eraseable]] +ERASEABLE string ftos_mindecimals(float number) { // inhibit stupid negative zero @@ -335,7 +453,7 @@ string ftos_mindecimals(float number) return sprintf("%.7g", number); } -[[eraseable]] +ERASEABLE int vercmp_recursive(string v1, string v2) { int dot1 = strstrofs(v1, ".", 0); @@ -354,7 +472,7 @@ int vercmp_recursive(string v1, string v2) else return (dot2 == -1) ? 1 : vercmp_recursive(substring(v1, dot1 + 1, 999), substring(v2, dot2 + 1, 999)); } -[[eraseable]] +ERASEABLE int vercmp(string v1, string v2) { if (strcasecmp(v1, v2) == 0) return 0; // early out check @@ -375,3 +493,67 @@ const string HEXDIGITS = "0123456789ABCDEF0123456789abcdef"; const string DIGITS = "0123456789"; #define IS_DIGIT(d) (strstrofs(DIGITS, (d), 0) >= 0) + +// returns true if the caret at position pos is escaped +ERASEABLE +bool isCaretEscaped(string theText, float pos) +{ + // count all the previous carets + int carets = 0; + while(pos - carets >= 1 && substring(theText, pos - carets - 1, 1) == "^") + ++carets; + // if number of previous carets is odd then this carets is escaped + return (carets & 1); +} + +ERASEABLE +bool isValidColorCodeValue(string theText, int cc_len, int tag_start) +{ + if (cc_len == 2) + return IS_DIGIT(substring(theText, tag_start + 1, 1)); + if (cc_len == 5) + return (IS_HEXDIGIT(substring(theText, tag_start + 2, 1)) + && IS_HEXDIGIT(substring(theText, tag_start + 3, 1)) + && IS_HEXDIGIT(substring(theText, tag_start + 4, 1))); + return false; +} + +// it returns 0 if pos is NOT in the middle or at the end of a color code +// otherwise it returns a vector with color code length as the first component +// and the offset from '^' position to pos as the second component +// e.g.: +// "j^2kl" | returns 0 if pos == 0 or 1 or 4 +// ^^ | returns '2 1' or '2 2' if pos == 2 or 3 +ERASEABLE +vector checkColorCode(string theText, int text_len, int pos, bool check_at_the_end) +{ + if (text_len == 0) + text_len = strlen(theText); + string tag_type = "^"; + int cc_len = 2; + int tag_len = 1; + + LABEL(check_color_tag) + + int ofs = cc_len; + if (!check_at_the_end) + ofs--; + for (; ofs >= 1; ofs--) + { + if (!(pos >= ofs && text_len >= pos + (cc_len - ofs))) + continue; + if(substring(theText, pos - ofs, tag_len) == tag_type) + { + if (!isCaretEscaped(theText, pos - ofs) && isValidColorCodeValue(theText, cc_len, pos - ofs)) + return eX * cc_len + eY * ofs; + } + } + if (cc_len == 2) + { + tag_type = "^x"; + cc_len = 5; + tag_len = 2; + goto check_color_tag; + } + return '0 0 0'; +}