| File: | String.c |
| Warning: | line 436, column 26 Dereference of null pointer |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | #include "String.h" | |||
| 2 | #include "Funcs.h" | |||
| 3 | #include "Logger.h" | |||
| 4 | #include "Platform.h" | |||
| 5 | #include "Stream.h" | |||
| 6 | #include "Utils.h" | |||
| 7 | ||||
| 8 | #ifdef __cplusplus | |||
| 9 | const cc_string String_Empty = { NULL((void*)0), 0, 0 }; | |||
| 10 | #else | |||
| 11 | const cc_string String_Empty; | |||
| 12 | #endif | |||
| 13 | ||||
| 14 | int String_CalcLen(const char* raw, int capacity) { | |||
| 15 | int length = 0; | |||
| 16 | while (length < capacity && *raw) { raw++; length++; } | |||
| 17 | return length; | |||
| 18 | } | |||
| 19 | ||||
| 20 | int String_Length(const char* raw) { | |||
| 21 | int length = 0; | |||
| 22 | while (length < UInt16_MaxValue((cc_uint16)65535) && *raw) { raw++; length++; } | |||
| 23 | return length; | |||
| 24 | } | |||
| 25 | ||||
| 26 | cc_string String_FromRaw(STRING_REF char* buffer, int capacity) { | |||
| 27 | return String_Init(buffer, String_CalcLen(buffer, capacity), capacity); | |||
| 28 | } | |||
| 29 | ||||
| 30 | cc_string String_FromReadonly(STRING_REF const char* buffer) { | |||
| 31 | int len = String_Length(buffer); | |||
| 32 | return String_Init((char*)buffer, len, len); | |||
| 33 | } | |||
| 34 | ||||
| 35 | ||||
| 36 | void String_Copy(cc_string* dst, const cc_string* src) { | |||
| 37 | dst->length = 0; | |||
| 38 | String_AppendString(dst, src); | |||
| 39 | } | |||
| 40 | ||||
| 41 | void String_CopyToRaw(char* dst, int capacity, const cc_string* src) { | |||
| 42 | int i, len = min(capacity, src->length)((capacity) < (src->length) ? (capacity) : (src->length )); | |||
| 43 | for (i = 0; i < len; i++) { dst[i] = src->buffer[i]; } | |||
| 44 | /* add \0 to mark end of used portion of buffer */ | |||
| 45 | if (len < capacity) dst[len] = '\0'; | |||
| 46 | } | |||
| 47 | ||||
| 48 | cc_string String_UNSAFE_Substring(STRING_REF const cc_string* str, int offset, int length) { | |||
| 49 | if (offset < 0 || offset > str->length) { | |||
| 50 | Logger_Abort("Offset for substring out of range"); | |||
| 51 | } | |||
| 52 | if (length < 0 || length > str->length) { | |||
| 53 | Logger_Abort("Length for substring out of range"); | |||
| 54 | } | |||
| 55 | if (offset + length > str->length) { | |||
| 56 | Logger_Abort("Result substring is out of range"); | |||
| 57 | } | |||
| 58 | return String_Init(str->buffer + offset, length, length); | |||
| 59 | } | |||
| 60 | ||||
| 61 | cc_string String_UNSAFE_SubstringAt(STRING_REF const cc_string* str, int offset) { | |||
| 62 | cc_string sub; | |||
| 63 | if (offset < 0 || offset > str->length) Logger_Abort("Sub offset out of range"); | |||
| 64 | ||||
| 65 | sub.buffer = str->buffer + offset; | |||
| 66 | sub.length = str->length - offset; | |||
| 67 | sub.capacity = str->length - offset; /* str->length to match String_UNSAFE_Substring */ | |||
| 68 | return sub; | |||
| 69 | } | |||
| 70 | ||||
| 71 | int String_UNSAFE_Split(STRING_REF const cc_string* str, char c, cc_string* subs, int maxSubs) { | |||
| 72 | int beg = 0, end, count, i; | |||
| 73 | ||||
| 74 | for (i = 0; i < maxSubs && beg <= str->length; i++) { | |||
| 75 | end = String_IndexOfAt(str, beg, c); | |||
| 76 | if (end == -1) end = str->length; | |||
| 77 | ||||
| 78 | subs[i] = String_UNSAFE_Substring(str, beg, end - beg); | |||
| 79 | beg = end + 1; | |||
| 80 | } | |||
| 81 | ||||
| 82 | count = i; | |||
| 83 | /* If not enough split substrings, make remaining NULL */ | |||
| 84 | for (; i < maxSubs; i++) { subs[i] = String_Empty; } | |||
| 85 | return count; | |||
| 86 | } | |||
| 87 | ||||
| 88 | void String_UNSAFE_SplitBy(STRING_REF cc_string* str, char c, cc_string* part) { | |||
| 89 | int idx = String_IndexOf(str, c)String_IndexOfAt(str, 0, c); | |||
| 90 | if (idx == -1) { | |||
| 91 | *part = *str; | |||
| 92 | *str = String_Empty; | |||
| 93 | } else { | |||
| 94 | *part = String_UNSAFE_Substring(str, 0, idx); idx++; | |||
| 95 | *str = String_UNSAFE_SubstringAt(str, idx); | |||
| 96 | } | |||
| 97 | } | |||
| 98 | ||||
| 99 | int String_UNSAFE_Separate(STRING_REF const cc_string* str, char c, cc_string* key, cc_string* value) { | |||
| 100 | int idx = String_IndexOf(str, c)String_IndexOfAt(str, 0, c); | |||
| 101 | if (idx == -1) { | |||
| 102 | *key = *str; | |||
| 103 | *value = String_Empty; | |||
| 104 | return false0; | |||
| 105 | } | |||
| 106 | ||||
| 107 | *key = String_UNSAFE_Substring(str, 0, idx); idx++; | |||
| 108 | *value = String_UNSAFE_SubstringAt(str, idx); | |||
| 109 | ||||
| 110 | /* Trim key [c] value to just key[c]value */ | |||
| 111 | String_UNSAFE_TrimEnd(key); | |||
| 112 | String_UNSAFE_TrimStart(value); | |||
| 113 | return key->length > 0 && value->length > 0; | |||
| 114 | } | |||
| 115 | ||||
| 116 | ||||
| 117 | int String_Equals(const cc_string* a, const cc_string* b) { | |||
| 118 | return a->length == b->length && Mem_Equal(a->buffer, b->buffer, a->length); | |||
| 119 | } | |||
| 120 | ||||
| 121 | int String_CaselessEquals(const cc_string* a, const cc_string* b) { | |||
| 122 | int i; | |||
| 123 | char aCur, bCur; | |||
| 124 | if (a->length != b->length) return false0; | |||
| 125 | ||||
| 126 | for (i = 0; i < a->length; i++) { | |||
| 127 | aCur = a->buffer[i]; Char_MakeLower(aCur)if ((aCur) >= 'A' && (aCur) <= 'Z') { (aCur) += ' '; }; | |||
| 128 | bCur = b->buffer[i]; Char_MakeLower(bCur)if ((bCur) >= 'A' && (bCur) <= 'Z') { (bCur) += ' '; }; | |||
| 129 | if (aCur != bCur) return false0; | |||
| 130 | } | |||
| 131 | return true1; | |||
| 132 | } | |||
| 133 | ||||
| 134 | int String_CaselessEqualsConst(const cc_string* a, const char* b) { | |||
| 135 | int i; | |||
| 136 | char aCur, bCur; | |||
| 137 | ||||
| 138 | for (i = 0; i < a->length; i++) { | |||
| 139 | aCur = a->buffer[i]; Char_MakeLower(aCur)if ((aCur) >= 'A' && (aCur) <= 'Z') { (aCur) += ' '; }; | |||
| 140 | bCur = b[i]; Char_MakeLower(bCur)if ((bCur) >= 'A' && (bCur) <= 'Z') { (bCur) += ' '; }; | |||
| 141 | if (aCur != bCur || bCur == '\0') return false0; | |||
| 142 | } | |||
| 143 | /* ensure at end of string */ | |||
| 144 | return b[a->length] == '\0'; | |||
| 145 | } | |||
| 146 | ||||
| 147 | ||||
| 148 | void String_Append(cc_string* str, char c) { | |||
| 149 | if (str->length == str->capacity) return; | |||
| 150 | str->buffer[str->length++] = c; | |||
| 151 | } | |||
| 152 | ||||
| 153 | void String_AppendBool(cc_string* str, cc_bool value) { | |||
| 154 | String_AppendConst(str, value ? "True" : "False"); | |||
| 155 | } | |||
| 156 | ||||
| 157 | int String_MakeUInt32(cc_uint32 num, char* digits) { | |||
| 158 | int len = 0; | |||
| 159 | do { | |||
| 160 | digits[len] = '0' + (num % 10); num /= 10; len++; | |||
| 161 | } while (num > 0); | |||
| 162 | return len; | |||
| 163 | } | |||
| 164 | ||||
| 165 | void String_AppendInt(cc_string* str, int num) { | |||
| 166 | if (num < 0) { | |||
| 167 | num = -num; | |||
| 168 | String_Append(str, '-'); | |||
| 169 | } | |||
| 170 | String_AppendUInt32(str, (cc_uint32)num); | |||
| 171 | } | |||
| 172 | ||||
| 173 | void String_AppendUInt32(cc_string* str, cc_uint32 num) { | |||
| 174 | char digits[STRING_INT_CHARS24]; | |||
| 175 | int i, count = String_MakeUInt32(num, digits); | |||
| 176 | ||||
| 177 | for (i = count - 1; i >= 0; i--) { | |||
| 178 | String_Append(str, digits[i]); | |||
| 179 | } | |||
| 180 | } | |||
| 181 | ||||
| 182 | void String_AppendPaddedInt(cc_string* str, int num, int minDigits) { | |||
| 183 | char digits[STRING_INT_CHARS24]; | |||
| 184 | int i, count; | |||
| 185 | for (i = 0; i < minDigits; i++) { digits[i] = '0'; } | |||
| 186 | ||||
| 187 | count = String_MakeUInt32(num, digits); | |||
| 188 | if (count < minDigits) count = minDigits; | |||
| 189 | ||||
| 190 | for (i = count - 1; i >= 0; i--) { | |||
| 191 | String_Append(str, digits[i]); | |||
| 192 | } | |||
| 193 | } | |||
| 194 | ||||
| 195 | void String_AppendFloat(cc_string* str, float num, int fracDigits) { | |||
| 196 | int i, whole, digit; | |||
| 197 | double frac; | |||
| 198 | ||||
| 199 | if (num < 0.0f) { | |||
| 200 | String_Append(str, '-'); /* don't need to check success */ | |||
| 201 | num = -num; | |||
| 202 | } | |||
| 203 | ||||
| 204 | whole = (int)num; | |||
| 205 | String_AppendUInt32(str, whole); | |||
| 206 | ||||
| 207 | frac = (double)num - (double)whole; | |||
| 208 | if (frac == 0.0) return; | |||
| 209 | String_Append(str, '.'); /* don't need to check success */ | |||
| 210 | ||||
| 211 | for (i = 0; i < fracDigits; i++) { | |||
| 212 | frac *= 10; | |||
| 213 | digit = (int)frac % 10; | |||
| 214 | String_Append(str, '0' + digit); | |||
| 215 | } | |||
| 216 | } | |||
| 217 | ||||
| 218 | void String_AppendHex(cc_string* str, cc_uint8 value) { | |||
| 219 | /* 48 = index of 0, 55 = index of (A - 10) */ | |||
| 220 | cc_uint8 hi = (value >> 4) & 0xF; | |||
| 221 | char c_hi = hi < 10 ? (hi + 48) : (hi + 55); | |||
| 222 | cc_uint8 lo = value & 0xF; | |||
| 223 | char c_lo = lo < 10 ? (lo + 48) : (lo + 55); | |||
| 224 | ||||
| 225 | String_Append(str, c_hi); | |||
| 226 | String_Append(str, c_lo); | |||
| 227 | } | |||
| 228 | ||||
| 229 | CC_NOINLINE__attribute__((noinline)) static void String_Hex32(cc_string* str, cc_uint32 value) { | |||
| 230 | int shift; | |||
| 231 | ||||
| 232 | for (shift = 24; shift >= 0; shift -= 8) { | |||
| 233 | cc_uint8 part = (cc_uint8)(value >> shift); | |||
| 234 | String_AppendHex(str, part); | |||
| 235 | } | |||
| 236 | } | |||
| 237 | ||||
| 238 | CC_NOINLINE__attribute__((noinline)) static void String_Hex64(cc_string* str, cc_uint64 value) { | |||
| 239 | int shift; | |||
| 240 | ||||
| 241 | for (shift = 56; shift >= 0; shift -= 8) { | |||
| 242 | cc_uint8 part = (cc_uint8)(value >> shift); | |||
| 243 | String_AppendHex(str, part); | |||
| 244 | } | |||
| 245 | } | |||
| 246 | ||||
| 247 | void String_AppendConst(cc_string* str, const char* src) { | |||
| 248 | for (; *src; src++) { | |||
| 249 | String_Append(str, *src); | |||
| 250 | } | |||
| 251 | } | |||
| 252 | ||||
| 253 | void String_AppendAll(cc_string* str, const void* data, int len) { | |||
| 254 | const char* src = (const char*)data; | |||
| 255 | int i; | |||
| 256 | for (i = 0; i < len; i++) String_Append(str, src[i]); | |||
| 257 | } | |||
| 258 | ||||
| 259 | void String_AppendString(cc_string* str, const cc_string* src) { | |||
| 260 | int i; | |||
| 261 | for (i = 0; i < src->length; i++) { | |||
| 262 | String_Append(str, src->buffer[i]); | |||
| 263 | } | |||
| 264 | } | |||
| 265 | ||||
| 266 | void String_AppendColorless(cc_string* str, const cc_string* src) { | |||
| 267 | char c; | |||
| 268 | int i; | |||
| 269 | ||||
| 270 | for (i = 0; i < src->length; i++) { | |||
| 271 | c = src->buffer[i]; | |||
| 272 | if (c == '&') { i++; continue; } /* Skip over the following colour code */ | |||
| 273 | String_Append(str, c); | |||
| 274 | } | |||
| 275 | } | |||
| 276 | ||||
| 277 | ||||
| 278 | int String_IndexOfAt(const cc_string* str, int offset, char c) { | |||
| 279 | int i; | |||
| 280 | for (i = offset; i < str->length; i++) { | |||
| 281 | if (str->buffer[i] == c) return i; | |||
| 282 | } | |||
| 283 | return -1; | |||
| 284 | } | |||
| 285 | ||||
| 286 | int String_LastIndexOfAt(const cc_string* str, int offset, char c) { | |||
| 287 | int i; | |||
| 288 | for (i = (str->length - 1) - offset; i >= 0; i--) { | |||
| 289 | if (str->buffer[i] == c) return i; | |||
| 290 | } | |||
| 291 | return -1; | |||
| 292 | } | |||
| 293 | ||||
| 294 | void String_InsertAt(cc_string* str, int offset, char c) { | |||
| 295 | int i; | |||
| 296 | ||||
| 297 | if (offset < 0 || offset > str->length) { | |||
| 298 | Logger_Abort("Offset for InsertAt out of range"); | |||
| 299 | } | |||
| 300 | if (str->length == str->capacity) { | |||
| 301 | Logger_Abort("Cannot insert character into full string"); | |||
| 302 | } | |||
| 303 | ||||
| 304 | for (i = str->length; i > offset; i--) { | |||
| 305 | str->buffer[i] = str->buffer[i - 1]; | |||
| 306 | } | |||
| 307 | str->buffer[offset] = c; | |||
| 308 | str->length++; | |||
| 309 | } | |||
| 310 | ||||
| 311 | void String_DeleteAt(cc_string* str, int offset) { | |||
| 312 | int i; | |||
| 313 | ||||
| 314 | if (offset < 0 || offset >= str->length) { | |||
| 315 | Logger_Abort("Offset for DeleteAt out of range"); | |||
| 316 | } | |||
| 317 | ||||
| 318 | for (i = offset; i < str->length - 1; i++) { | |||
| 319 | str->buffer[i] = str->buffer[i + 1]; | |||
| 320 | } | |||
| 321 | str->buffer[str->length - 1] = '\0'; | |||
| 322 | str->length--; | |||
| 323 | } | |||
| 324 | ||||
| 325 | void String_UNSAFE_TrimStart(cc_string* str) { | |||
| 326 | int i; | |||
| 327 | for (i = 0; i < str->length; i++) { | |||
| 328 | if (str->buffer[i] != ' ') break; | |||
| 329 | ||||
| 330 | str->buffer++; | |||
| 331 | str->length--; i--; | |||
| 332 | } | |||
| 333 | } | |||
| 334 | ||||
| 335 | void String_UNSAFE_TrimEnd(cc_string* str) { | |||
| 336 | int i; | |||
| 337 | for (i = str->length - 1; i >= 0; i--) { | |||
| 338 | if (str->buffer[i] != ' ') break; | |||
| 339 | str->length--; | |||
| 340 | } | |||
| 341 | } | |||
| 342 | ||||
| 343 | int String_IndexOfConst(const cc_string* str, const char* sub) { | |||
| 344 | int i, j; | |||
| 345 | ||||
| 346 | for (i = 0; i < str->length; i++) { | |||
| 347 | for (j = 0; sub[j] && (i + j) < str->length; j++) { | |||
| 348 | ||||
| 349 | if (str->buffer[i + j] != sub[j]) break; | |||
| 350 | } | |||
| 351 | if (sub[j] == '\0') return i; | |||
| 352 | } | |||
| 353 | return -1; | |||
| 354 | } | |||
| 355 | ||||
| 356 | int String_CaselessContains(const cc_string* str, const cc_string* sub) { | |||
| 357 | char strCur, subCur; | |||
| 358 | int i, j; | |||
| 359 | ||||
| 360 | for (i = 0; i < str->length; i++) { | |||
| 361 | for (j = 0; j < sub->length && (i + j) < str->length; j++) { | |||
| 362 | ||||
| 363 | strCur = str->buffer[i + j]; Char_MakeLower(strCur)if ((strCur) >= 'A' && (strCur) <= 'Z') { (strCur ) += ' '; }; | |||
| 364 | subCur = sub->buffer[j]; Char_MakeLower(subCur)if ((subCur) >= 'A' && (subCur) <= 'Z') { (subCur ) += ' '; }; | |||
| 365 | if (strCur != subCur) break; | |||
| 366 | } | |||
| 367 | if (j == sub->length) return true1; | |||
| 368 | } | |||
| 369 | return false0; | |||
| 370 | } | |||
| 371 | ||||
| 372 | int String_CaselessStarts(const cc_string* str, const cc_string* sub) { | |||
| 373 | char strCur, subCur; | |||
| 374 | int i; | |||
| 375 | if (str->length < sub->length) return false0; | |||
| 376 | ||||
| 377 | for (i = 0; i < sub->length; i++) { | |||
| 378 | strCur = str->buffer[i]; Char_MakeLower(strCur)if ((strCur) >= 'A' && (strCur) <= 'Z') { (strCur ) += ' '; }; | |||
| 379 | subCur = sub->buffer[i]; Char_MakeLower(subCur)if ((subCur) >= 'A' && (subCur) <= 'Z') { (subCur ) += ' '; }; | |||
| 380 | if (strCur != subCur) return false0; | |||
| 381 | } | |||
| 382 | return true1; | |||
| 383 | } | |||
| 384 | ||||
| 385 | int String_CaselessEnds(const cc_string* str, const cc_string* sub) { | |||
| 386 | char strCur, subCur; | |||
| 387 | int i, j = str->length - sub->length; | |||
| 388 | if (j < 0) return false0; /* sub longer than str */ | |||
| 389 | ||||
| 390 | for (i = 0; i < sub->length; i++) { | |||
| 391 | strCur = str->buffer[j + i]; Char_MakeLower(strCur)if ((strCur) >= 'A' && (strCur) <= 'Z') { (strCur ) += ' '; }; | |||
| 392 | subCur = sub->buffer[i]; Char_MakeLower(subCur)if ((subCur) >= 'A' && (subCur) <= 'Z') { (subCur ) += ' '; }; | |||
| 393 | if (strCur != subCur) return false0; | |||
| 394 | } | |||
| 395 | return true1; | |||
| 396 | } | |||
| 397 | ||||
| 398 | int String_Compare(const cc_string* a, const cc_string* b) { | |||
| 399 | char aCur, bCur; | |||
| 400 | int i, minLen = min(a->length, b->length)((a->length) < (b->length) ? (a->length) : (b-> length)); | |||
| 401 | ||||
| 402 | for (i = 0; i < minLen; i++) { | |||
| 403 | aCur = a->buffer[i]; Char_MakeLower(aCur)if ((aCur) >= 'A' && (aCur) <= 'Z') { (aCur) += ' '; }; | |||
| 404 | bCur = b->buffer[i]; Char_MakeLower(bCur)if ((bCur) >= 'A' && (bCur) <= 'Z') { (bCur) += ' '; }; | |||
| 405 | ||||
| 406 | if (aCur == bCur) continue; | |||
| 407 | return (cc_uint8)aCur - (cc_uint8)bCur; | |||
| 408 | } | |||
| 409 | ||||
| 410 | /* all chars are equal here - same string, or a substring */ | |||
| 411 | return a->length - b->length; | |||
| 412 | } | |||
| 413 | ||||
| 414 | void String_Format1(cc_string* str, const char* format, const void* a1) { | |||
| 415 | String_Format4(str, format, a1, NULL((void*)0), NULL((void*)0), NULL((void*)0)); | |||
| ||||
| 416 | } | |||
| 417 | void String_Format2(cc_string* str, const char* format, const void* a1, const void* a2) { | |||
| 418 | String_Format4(str, format, a1, a2, NULL((void*)0), NULL((void*)0)); | |||
| 419 | } | |||
| 420 | void String_Format3(cc_string* str, const char* format, const void* a1, const void* a2, const void* a3) { | |||
| 421 | String_Format4(str, format, a1, a2, a3, NULL((void*)0)); | |||
| 422 | } | |||
| 423 | void String_Format4(cc_string* str, const char* format, const void* a1, const void* a2, const void* a3, const void* a4) { | |||
| 424 | const void* arg; | |||
| 425 | int i, j = 0, digits; | |||
| 426 | ||||
| 427 | const void* args[4]; | |||
| 428 | args[0] = a1; args[1] = a2; args[2] = a3; args[3] = a4; | |||
| 429 | ||||
| 430 | for (i = 0; format[i]; i++) { | |||
| 431 | if (format[i] != '%') { String_Append(str, format[i]); continue; } | |||
| 432 | arg = args[j++]; | |||
| 433 | ||||
| 434 | switch (format[++i]) { | |||
| 435 | case 'b': | |||
| 436 | String_AppendInt(str, *((cc_uint8*)arg)); break; | |||
| ||||
| 437 | case 'i': | |||
| 438 | String_AppendInt(str, *((int*)arg)); break; | |||
| 439 | case 'f': | |||
| 440 | digits = format[++i] - '0'; | |||
| 441 | String_AppendFloat(str, *((float*)arg), digits); break; | |||
| 442 | case 'p': | |||
| 443 | digits = format[++i] - '0'; | |||
| 444 | String_AppendPaddedInt(str, *((int*)arg), digits); break; | |||
| 445 | case 't': | |||
| 446 | String_AppendBool(str, *((cc_bool*)arg)); break; | |||
| 447 | case 'c': | |||
| 448 | String_AppendConst(str, (char*)arg); break; | |||
| 449 | case 's': | |||
| 450 | String_AppendString(str, (cc_string*)arg); break; | |||
| 451 | case 'r': | |||
| 452 | String_Append(str, *((char*)arg)); break; | |||
| 453 | case 'x': | |||
| 454 | if (sizeof(cc_uintptr) == 4) { | |||
| 455 | String_Hex32(str, *((cc_uint32*)arg)); break; | |||
| 456 | } else { | |||
| 457 | String_Hex64(str, *((cc_uint64*)arg)); break; | |||
| 458 | } | |||
| 459 | case 'h': | |||
| 460 | String_Hex32(str, *((cc_uint32*)arg)); break; | |||
| 461 | case '%': | |||
| 462 | String_Append(str, '%'); break; | |||
| 463 | default: | |||
| 464 | Logger_Abort("Invalid type for string format"); | |||
| 465 | } | |||
| 466 | } | |||
| 467 | } | |||
| 468 | ||||
| 469 | ||||
| 470 | /*########################################################################################################################* | |||
| 471 | *------------------------------------------------Character set conversions------------------------------------------------* | |||
| 472 | *#########################################################################################################################*/ | |||
| 473 | static const cc_unichar controlChars[32] = { | |||
| 474 | 0x0000, 0x263A, 0x263B, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, | |||
| 475 | 0x25D8, 0x25CB, 0x25D9, 0x2642, 0x2640, 0x266A, 0x266B, 0x263C, | |||
| 476 | 0x25BA, 0x25C4, 0x2195, 0x203C, 0x00B6, 0x00A7, 0x25AC, 0x21A8, | |||
| 477 | 0x2191, 0x2193, 0x2192, 0x2190, 0x221F, 0x2194, 0x25B2, 0x25BC | |||
| 478 | }; | |||
| 479 | ||||
| 480 | static const cc_unichar extendedChars[129] = { 0x2302, | |||
| 481 | 0x00C7, 0x00FC, 0x00E9, 0x00E2, 0x00E4, 0x00E0, 0x00E5, 0x00E7, | |||
| 482 | 0x00EA, 0x00EB, 0x00E8, 0x00EF, 0x00EE, 0x00EC, 0x00C4, 0x00C5, | |||
| 483 | 0x00C9, 0x00E6, 0x00C6, 0x00F4, 0x00F6, 0x00F2, 0x00FB, 0x00F9, | |||
| 484 | 0x00FF, 0x00D6, 0x00DC, 0x00A2, 0x00A3, 0x00A5, 0x20A7, 0x0192, | |||
| 485 | 0x00E1, 0x00ED, 0x00F3, 0x00FA, 0x00F1, 0x00D1, 0x00AA, 0x00BA, | |||
| 486 | 0x00BF, 0x2310, 0x00AC, 0x00BD, 0x00BC, 0x00A1, 0x00AB, 0x00BB, | |||
| 487 | 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, | |||
| 488 | 0x2555, 0x2563, 0x2551, 0x2557, 0x255D, 0x255C, 0x255B, 0x2510, | |||
| 489 | 0x2514, 0x2534, 0x252C, 0x251C, 0x2500, 0x253C, 0x255E, 0x255F, | |||
| 490 | 0x255A, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256C, 0x2567, | |||
| 491 | 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256B, | |||
| 492 | 0x256A, 0x2518, 0x250C, 0x2588, 0x2584, 0x258C, 0x2590, 0x2580, | |||
| 493 | 0x03B1, 0x00DF, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, | |||
| 494 | 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x221E, 0x03C6, 0x03B5, 0x2229, | |||
| 495 | 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, | |||
| 496 | 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x25A0, 0x00A0 | |||
| 497 | }; | |||
| 498 | ||||
| 499 | cc_unichar Convert_CP437ToUnicode(char c) { | |||
| 500 | cc_uint8 raw = (cc_uint8)c; | |||
| 501 | if (raw < 0x20) return controlChars[raw]; | |||
| 502 | if (raw < 0x7F) return raw; | |||
| 503 | return extendedChars[raw - 0x7F]; | |||
| 504 | } | |||
| 505 | ||||
| 506 | char Convert_CodepointToCP437(cc_codepoint cp) { | |||
| 507 | char c; Convert_TryCodepointToCP437(cp, &c); return c; | |||
| 508 | } | |||
| 509 | ||||
| 510 | static cc_codepoint ReduceEmoji(cc_codepoint cp) { | |||
| 511 | if (cp == 0x1F31E) return 0x263C; | |||
| 512 | if (cp == 0x1F3B5) return 0x266B; | |||
| 513 | if (cp == 0x1F642) return 0x263A; | |||
| 514 | ||||
| 515 | if (cp == 0x1F600 || cp == 0x1F601 || cp == 0x1F603) return 0x263A; | |||
| 516 | if (cp == 0x1F604 || cp == 0x1F606 || cp == 0x1F60A) return 0x263A; | |||
| 517 | return cp; | |||
| 518 | } | |||
| 519 | ||||
| 520 | cc_bool Convert_TryCodepointToCP437(cc_codepoint cp, char* c) { | |||
| 521 | int i; | |||
| 522 | if (cp >= 0x20 && cp < 0x7F) { *c = (char)cp; return true1; } | |||
| 523 | if (cp >= 0x1F000) cp = ReduceEmoji(cp); | |||
| 524 | ||||
| 525 | for (i = 0; i < Array_Elems(controlChars)(sizeof(controlChars) / sizeof(controlChars[0])); i++) { | |||
| 526 | if (controlChars[i] == cp) { *c = i; return true1; } | |||
| 527 | } | |||
| 528 | for (i = 0; i < Array_Elems(extendedChars)(sizeof(extendedChars) / sizeof(extendedChars[0])); i++) { | |||
| 529 | if (extendedChars[i] == cp) { *c = i + 0x7F; return true1; } | |||
| 530 | } | |||
| 531 | ||||
| 532 | *c = '?'; return false0; | |||
| 533 | } | |||
| 534 | ||||
| 535 | int Convert_Utf8ToCodepoint(cc_codepoint* cp, const cc_uint8* data, cc_uint32 len) { | |||
| 536 | *cp = '\0'; | |||
| 537 | if (!len) return 0; | |||
| 538 | ||||
| 539 | if (data[0] <= 0x7F) { | |||
| 540 | *cp = data[0]; | |||
| 541 | return 1; | |||
| 542 | } else if ((data[0] & 0xE0) == 0xC0) { | |||
| 543 | if (len < 2) return 0; | |||
| 544 | ||||
| 545 | *cp = ((data[0] & 0x1F) << 6) | ((data[1] & 0x3F)); | |||
| 546 | return 2; | |||
| 547 | } else if ((data[0] & 0xF0) == 0xE0) { | |||
| 548 | if (len < 3) return 0; | |||
| 549 | ||||
| 550 | *cp = ((data[0] & 0x0F) << 12) | ((data[1] & 0x3F) << 6) | |||
| 551 | | ((data[2] & 0x3F)); | |||
| 552 | return 3; | |||
| 553 | } else { | |||
| 554 | if (len < 4) return 0; | |||
| 555 | ||||
| 556 | *cp = ((data[0] & 0x07) << 18) | ((data[1] & 0x3F) << 12) | |||
| 557 | | ((data[2] & 0x3F) << 6) | (data[3] & 0x3F); | |||
| 558 | return 4; | |||
| 559 | } | |||
| 560 | } | |||
| 561 | ||||
| 562 | /* Encodes a unicode character in UTF8, returning number of bytes written */ | |||
| 563 | static int Convert_UnicodeToUtf8(cc_unichar uc, cc_uint8* data) { | |||
| 564 | if (uc <= 0x7F) { | |||
| 565 | data[0] = (cc_uint8)uc; | |||
| 566 | return 1; | |||
| 567 | } else if (uc <= 0x7FF) { | |||
| 568 | data[0] = 0xC0 | ((uc >> 6) & 0x1F); | |||
| 569 | data[1] = 0x80 | ((uc) & 0x3F); | |||
| 570 | return 2; | |||
| 571 | } else { | |||
| 572 | data[0] = 0xE0 | ((uc >> 12) & 0x0F); | |||
| 573 | data[1] = 0x80 | ((uc >> 6) & 0x3F); | |||
| 574 | data[2] = 0x80 | ((uc) & 0x3F); | |||
| 575 | return 3; | |||
| 576 | } | |||
| 577 | } | |||
| 578 | ||||
| 579 | int Convert_CP437ToUtf8(char c, cc_uint8* data) { | |||
| 580 | /* Common ASCII case */ | |||
| 581 | if (c >= 0x20 && c < 0x7F) { | |||
| 582 | data[0] = (cc_uint8)c; | |||
| 583 | return 1; | |||
| 584 | } | |||
| 585 | return Convert_UnicodeToUtf8(Convert_CP437ToUnicode(c), data); | |||
| 586 | } | |||
| 587 | ||||
| 588 | void String_AppendUtf16(cc_string* value, const void* data, int numBytes) { | |||
| 589 | const cc_unichar* chars = (const cc_unichar*)data; | |||
| 590 | int i; char c; | |||
| 591 | ||||
| 592 | for (i = 0; i < (numBytes >> 1); i++) { | |||
| 593 | /* TODO: UTF16 to codepoint conversion */ | |||
| 594 | if (Convert_TryCodepointToCP437(chars[i], &c)) String_Append(value, c); | |||
| 595 | } | |||
| 596 | } | |||
| 597 | ||||
| 598 | void String_AppendUtf8(cc_string* value, const void* data, int numBytes) { | |||
| 599 | const cc_uint8* chars = (const cc_uint8*)data; | |||
| 600 | int len; cc_codepoint cp; char c; | |||
| 601 | ||||
| 602 | for (; numBytes > 0; numBytes -= len) { | |||
| 603 | len = Convert_Utf8ToCodepoint(&cp, chars, numBytes); | |||
| 604 | if (!len) return; | |||
| 605 | ||||
| 606 | if (Convert_TryCodepointToCP437(cp, &c)) String_Append(value, c); | |||
| 607 | chars += len; | |||
| 608 | } | |||
| 609 | } | |||
| 610 | ||||
| 611 | void String_DecodeCP1252(cc_string* value, const void* data, int numBytes) { | |||
| 612 | const cc_uint8* chars = (const cc_uint8*)data; | |||
| 613 | int i; char c; | |||
| 614 | ||||
| 615 | for (i = 0; i < numBytes; i++) { | |||
| 616 | if (Convert_TryCodepointToCP437(chars[i], &c)) String_Append(value, c); | |||
| 617 | } | |||
| 618 | } | |||
| 619 | ||||
| 620 | ||||
| 621 | /*########################################################################################################################* | |||
| 622 | *--------------------------------------------------Numerical conversions--------------------------------------------------* | |||
| 623 | *#########################################################################################################################*/ | |||
| 624 | cc_bool Convert_ParseUInt8(const cc_string* str, cc_uint8* value) { | |||
| 625 | int tmp; | |||
| 626 | *value = 0; | |||
| 627 | if (!Convert_ParseInt(str, &tmp) || tmp < 0 || tmp > UInt8_MaxValue((cc_uint8)255)) return false0; | |||
| 628 | *value = (cc_uint8)tmp; return true1; | |||
| 629 | } | |||
| 630 | ||||
| 631 | cc_bool Convert_ParseUInt16(const cc_string* str, cc_uint16* value) { | |||
| 632 | int tmp; | |||
| 633 | *value = 0; | |||
| 634 | if (!Convert_ParseInt(str, &tmp) || tmp < 0 || tmp > UInt16_MaxValue((cc_uint16)65535)) return false0; | |||
| 635 | *value = (cc_uint16)tmp; return true1; | |||
| 636 | } | |||
| 637 | ||||
| 638 | static int Convert_CompareDigits(const char* digits, const char* magnitude) { | |||
| 639 | int i; | |||
| 640 | for (i = 0; ; i++) { | |||
| 641 | if (magnitude[i] == '\0') return 0; | |||
| 642 | if (digits[i] > magnitude[i]) return 1; | |||
| 643 | if (digits[i] < magnitude[i]) return -1; | |||
| 644 | } | |||
| 645 | return 0; | |||
| 646 | } | |||
| 647 | ||||
| 648 | static cc_bool Convert_TryParseDigits(const cc_string* str, cc_bool* negative, char* digits, int maxDigits) { | |||
| 649 | char* start = digits; | |||
| 650 | int offset = 0, i; | |||
| 651 | ||||
| 652 | *negative = false0; | |||
| 653 | if (!str->length) return false0; | |||
| 654 | digits += (maxDigits - 1); | |||
| 655 | ||||
| 656 | /* Handle number signs */ | |||
| 657 | if (str->buffer[0] == '-') { *negative = true1; offset = 1; } | |||
| 658 | if (str->buffer[0] == '+') { offset = 1; } | |||
| 659 | ||||
| 660 | /* add digits, starting at last digit */ | |||
| 661 | for (i = str->length - 1; i >= offset; i--) { | |||
| 662 | char c = str->buffer[i]; | |||
| 663 | if (c < '0' || c > '9' || digits < start) return false0; | |||
| 664 | *digits-- = c; | |||
| 665 | } | |||
| 666 | ||||
| 667 | for (; digits >= start; ) { *digits-- = '0'; } | |||
| 668 | return true1; | |||
| 669 | } | |||
| 670 | ||||
| 671 | #define INT32_DIGITS10 10 | |||
| 672 | cc_bool Convert_ParseInt(const cc_string* str, int* value) { | |||
| 673 | cc_bool negative; | |||
| 674 | char digits[INT32_DIGITS10]; | |||
| 675 | int i, compare, sum = 0; | |||
| 676 | ||||
| 677 | *value = 0; | |||
| 678 | if (!Convert_TryParseDigits(str, &negative, digits, INT32_DIGITS10)) return false0; | |||
| 679 | ||||
| 680 | if (negative) { | |||
| 681 | compare = Convert_CompareDigits(digits, "2147483648"); | |||
| 682 | /* Special case, since |largest min value| is > |largest max value| */ | |||
| 683 | if (compare == 0) { *value = Int32_MinValue((cc_int32)-2147483647L - (cc_int32)1L); return true1; } | |||
| 684 | } else { | |||
| 685 | compare = Convert_CompareDigits(digits, "2147483647"); | |||
| 686 | } | |||
| 687 | if (compare > 0) return false0; | |||
| 688 | ||||
| 689 | for (i = 0; i < INT32_DIGITS10; i++) { | |||
| 690 | sum *= 10; sum += digits[i] - '0'; | |||
| 691 | } | |||
| 692 | ||||
| 693 | if (negative) sum = -sum; | |||
| 694 | *value = sum; | |||
| 695 | return true1; | |||
| 696 | } | |||
| 697 | ||||
| 698 | #define UINT64_DIGITS20 20 | |||
| 699 | cc_bool Convert_ParseUInt64(const cc_string* str, cc_uint64* value) { | |||
| 700 | cc_bool negative; | |||
| 701 | char digits[UINT64_DIGITS20]; | |||
| 702 | int i, compare; | |||
| 703 | cc_uint64 sum = 0; | |||
| 704 | ||||
| 705 | *value = 0; | |||
| 706 | if (!Convert_TryParseDigits(str, &negative, digits, UINT64_DIGITS20)) return false0; | |||
| 707 | ||||
| 708 | compare = Convert_CompareDigits(digits, "18446744073709551615"); | |||
| 709 | if (negative || compare > 0) return false0; | |||
| 710 | ||||
| 711 | for (i = 0; i < UINT64_DIGITS20; i++) { | |||
| 712 | sum *= 10; sum += digits[i] - '0'; | |||
| 713 | } | |||
| 714 | ||||
| 715 | *value = sum; | |||
| 716 | return true1; | |||
| 717 | } | |||
| 718 | ||||
| 719 | cc_bool Convert_ParseFloat(const cc_string* str, float* value) { | |||
| 720 | int i = 0; | |||
| 721 | cc_bool negate = false0; | |||
| 722 | double sum, whole, fract, divide = 10.0; | |||
| 723 | *value = 0.0f; | |||
| 724 | ||||
| 725 | /* Handle number signs */ | |||
| 726 | if (!str->length) return false0; | |||
| 727 | if (str->buffer[0] == '-') { negate = true1; i++; } | |||
| 728 | if (str->buffer[0] == '+') { i++; } | |||
| 729 | ||||
| 730 | for (whole = 0.0; i < str->length; i++) { | |||
| 731 | char c = str->buffer[i]; | |||
| 732 | if (c == '.' || c == ',') { i++; break; } | |||
| 733 | ||||
| 734 | if (c < '0' || c > '9') return false0; | |||
| 735 | whole *= 10; whole += (c - '0'); | |||
| 736 | } | |||
| 737 | ||||
| 738 | for (fract = 0.0; i < str->length; i++) { | |||
| 739 | char c = str->buffer[i]; | |||
| 740 | if (c < '0' || c > '9') return false0; | |||
| 741 | ||||
| 742 | fract += (c - '0') / divide; divide *= 10; | |||
| 743 | } | |||
| 744 | ||||
| 745 | sum = whole + fract; | |||
| 746 | if (negate) sum = -sum; | |||
| 747 | *value = (float)sum; | |||
| 748 | return true1; | |||
| 749 | } | |||
| 750 | ||||
| 751 | cc_bool Convert_ParseBool(const cc_string* str, cc_bool* value) { | |||
| 752 | if (String_CaselessEqualsConst(str, "True")) { | |||
| 753 | *value = true1; return true1; | |||
| 754 | } | |||
| 755 | if (String_CaselessEqualsConst(str, "False")) { | |||
| 756 | *value = false0; return true1; | |||
| 757 | } | |||
| 758 | *value = false0; return false0; | |||
| 759 | } | |||
| 760 | ||||
| 761 | ||||
| 762 | /*########################################################################################################################* | |||
| 763 | *------------------------------------------------------StringsBuffer------------------------------------------------------* | |||
| 764 | *#########################################################################################################################*/ | |||
| 765 | #define STRINGSBUFFER_BUFFER_EXPAND_SIZE8192 8192 | |||
| 766 | ||||
| 767 | #define StringsBuffer_GetOffset(raw)((raw) >> buffer->_lenShift) ((raw) >> buffer->_lenShift) | |||
| 768 | #define StringsBuffer_GetLength(raw)((raw) & buffer->_lenMask) ((raw) & buffer->_lenMask) | |||
| 769 | #define StringsBuffer_PackOffset(off)((off) << buffer->_lenShift) ((off) << buffer->_lenShift) | |||
| 770 | ||||
| 771 | CC_NOINLINE__attribute__((noinline)) static void StringsBuffer_Init(struct StringsBuffer* buffer) { | |||
| 772 | buffer->count = 0; | |||
| 773 | buffer->totalLength = 0; | |||
| 774 | buffer->textBuffer = buffer->_defaultBuffer; | |||
| 775 | buffer->flagsBuffer = buffer->_defaultFlags; | |||
| 776 | buffer->_textCapacity = STRINGSBUFFER_BUFFER_DEF_SIZE4096; | |||
| 777 | buffer->_flagsCapacity = STRINGSBUFFER_FLAGS_DEF_ELEMS256; | |||
| 778 | ||||
| 779 | if (buffer->_lenShift) return; | |||
| 780 | StringsBuffer_SetLengthBits(buffer, STRINGSBUFFER_DEF_LEN_SHIFT9); | |||
| 781 | } | |||
| 782 | ||||
| 783 | void StringsBuffer_SetLengthBits(struct StringsBuffer* buffer, int bits) { | |||
| 784 | buffer->_lenShift = bits; | |||
| 785 | buffer->_lenMask = (1 << bits) - 1; | |||
| 786 | } | |||
| 787 | ||||
| 788 | void StringsBuffer_Clear(struct StringsBuffer* buffer) { | |||
| 789 | /* Never initialised to begin with */ | |||
| 790 | if (!buffer->_flagsCapacity) return; | |||
| 791 | ||||
| 792 | if (buffer->textBuffer != buffer->_defaultBuffer) { | |||
| 793 | Mem_Free(buffer->textBuffer); | |||
| 794 | } | |||
| 795 | if (buffer->flagsBuffer != buffer->_defaultFlags) { | |||
| 796 | Mem_Free(buffer->flagsBuffer); | |||
| 797 | } | |||
| 798 | StringsBuffer_Init(buffer); | |||
| 799 | } | |||
| 800 | ||||
| 801 | cc_string StringsBuffer_UNSAFE_Get(struct StringsBuffer* buffer, int i) { | |||
| 802 | cc_uint32 flags, offset, len; | |||
| 803 | if (i < 0 || i >= buffer->count) Logger_Abort("Tried to get String past StringsBuffer end"); | |||
| 804 | ||||
| 805 | flags = buffer->flagsBuffer[i]; | |||
| 806 | offset = StringsBuffer_GetOffset(flags)((flags) >> buffer->_lenShift); | |||
| 807 | len = StringsBuffer_GetLength(flags)((flags) & buffer->_lenMask); | |||
| 808 | return String_Init(&buffer->textBuffer[offset], len, len); | |||
| 809 | } | |||
| 810 | ||||
| 811 | void StringsBuffer_UNSAFE_GetRaw(struct StringsBuffer* buffer, int i, cc_string* dst) { | |||
| 812 | cc_uint32 flags = buffer->flagsBuffer[i]; | |||
| 813 | dst->buffer = buffer->textBuffer + StringsBuffer_GetOffset(flags)((flags) >> buffer->_lenShift); | |||
| 814 | dst->length = StringsBuffer_GetLength(flags)((flags) & buffer->_lenMask); | |||
| 815 | dst->capacity = 0; | |||
| 816 | } | |||
| 817 | ||||
| 818 | void StringsBuffer_Add(struct StringsBuffer* buffer, const cc_string* str) { | |||
| 819 | int textOffset; | |||
| 820 | /* StringsBuffer hasn't been initialised yet, do it here */ | |||
| 821 | if (!buffer->_flagsCapacity) StringsBuffer_Init(buffer); | |||
| 822 | ||||
| 823 | if (buffer->count == buffer->_flagsCapacity) { | |||
| 824 | Utils_Resize((void**)&buffer->flagsBuffer, &buffer->_flagsCapacity, | |||
| 825 | 4, STRINGSBUFFER_FLAGS_DEF_ELEMS256, 512); | |||
| 826 | } | |||
| 827 | ||||
| 828 | if (str->length > buffer->_lenMask) { | |||
| 829 | Logger_Abort("String too big to insert into StringsBuffer"); | |||
| 830 | } | |||
| 831 | ||||
| 832 | textOffset = buffer->totalLength; | |||
| 833 | if (textOffset + str->length >= buffer->_textCapacity) { | |||
| 834 | Utils_Resize((void**)&buffer->textBuffer, &buffer->_textCapacity, | |||
| 835 | 1, STRINGSBUFFER_BUFFER_DEF_SIZE4096, 8192); | |||
| 836 | } | |||
| 837 | ||||
| 838 | Mem_Copy(&buffer->textBuffer[textOffset], str->buffer, str->length); | |||
| 839 | buffer->flagsBuffer[buffer->count] = str->length | StringsBuffer_PackOffset(textOffset)((textOffset) << buffer->_lenShift); | |||
| 840 | ||||
| 841 | buffer->count++; | |||
| 842 | buffer->totalLength += str->length; | |||
| 843 | } | |||
| 844 | ||||
| 845 | void StringsBuffer_Remove(struct StringsBuffer* buffer, int index) { | |||
| 846 | cc_uint32 flags, offset, len; | |||
| 847 | cc_uint32 i, offsetAdj; | |||
| 848 | if (index < 0 || index >= buffer->count) Logger_Abort("Tried to remove String past StringsBuffer end"); | |||
| 849 | ||||
| 850 | flags = buffer->flagsBuffer[index]; | |||
| 851 | offset = StringsBuffer_GetOffset(flags)((flags) >> buffer->_lenShift); | |||
| 852 | len = StringsBuffer_GetLength(flags)((flags) & buffer->_lenMask); | |||
| 853 | ||||
| 854 | /* Imagine buffer is this: AAXXYYZZ, and want to delete XX */ | |||
| 855 | /* We iterate from first char of Y to last char of Z, */ | |||
| 856 | /* shifting each character two to the left. */ | |||
| 857 | for (i = offset + len; i < buffer->totalLength; i++) { | |||
| 858 | buffer->textBuffer[i - len] = buffer->textBuffer[i]; | |||
| 859 | } | |||
| 860 | ||||
| 861 | /* Adjust text offset of elements after this element */ | |||
| 862 | /* Elements may not be in order so must account for that */ | |||
| 863 | offsetAdj = StringsBuffer_PackOffset(len)((len) << buffer->_lenShift); | |||
| 864 | for (i = index; i < buffer->count - 1; i++) { | |||
| 865 | buffer->flagsBuffer[i] = buffer->flagsBuffer[i + 1]; | |||
| 866 | if (buffer->flagsBuffer[i] >= flags) { | |||
| 867 | buffer->flagsBuffer[i] -= offsetAdj; | |||
| 868 | } | |||
| 869 | } | |||
| 870 | ||||
| 871 | buffer->count--; | |||
| 872 | buffer->totalLength -= len; | |||
| 873 | } | |||
| 874 | ||||
| 875 | ||||
| 876 | /*########################################################################################################################* | |||
| 877 | *------------------------------------------------------Word wrapper-------------------------------------------------------* | |||
| 878 | *#########################################################################################################################*/ | |||
| 879 | static cc_bool WordWrap_IsWrapper(char c) { | |||
| 880 | return c == '\0' || c == ' ' || c == '-' || c == '>' || c == '<' || c == '/' || c == '\\'; | |||
| 881 | } | |||
| 882 | ||||
| 883 | void WordWrap_Do(STRING_REF cc_string* text, cc_string* lines, int numLines, int lineLen) { | |||
| 884 | int i, lineStart, lineEnd; | |||
| 885 | for (i = 0; i < numLines; i++) { lines[i] = String_Empty; } | |||
| 886 | ||||
| 887 | for (i = 0, lineStart = 0; i < numLines; i++) { | |||
| 888 | int nextLineStart = lineStart + lineLen; | |||
| 889 | /* No more text to wrap */ | |||
| 890 | if (nextLineStart >= text->length) { | |||
| 891 | lines[i] = String_UNSAFE_SubstringAt(text, lineStart); return; | |||
| 892 | } | |||
| 893 | ||||
| 894 | /* Find beginning of last word on current line */ | |||
| 895 | for (lineEnd = nextLineStart; lineEnd >= lineStart; lineEnd--) { | |||
| 896 | if (WordWrap_IsWrapper(text->buffer[lineEnd])) break; | |||
| 897 | } | |||
| 898 | lineEnd++; /* move after wrapper char (i.e. beginning of last word) */ | |||
| 899 | ||||
| 900 | if (lineEnd <= lineStart || lineEnd >= nextLineStart) { | |||
| 901 | /* Three special cases handled by this: */ | |||
| 902 | /* - Entire line is filled with a single word */ | |||
| 903 | /* - Last character(s) on current line are wrapper characters */ | |||
| 904 | /* - First character on next line is a wrapper character (last word ends at current line end) */ | |||
| 905 | lines[i] = String_UNSAFE_Substring(text, lineStart, lineLen); | |||
| 906 | lineStart += lineLen; | |||
| 907 | } else { | |||
| 908 | /* Last word in current line does not end in current line (extends onto next line) */ | |||
| 909 | /* Trim current line to end at beginning of last word */ | |||
| 910 | /* Set next line to start at beginning of last word */ | |||
| 911 | lines[i] = String_UNSAFE_Substring(text, lineStart, lineEnd - lineStart); | |||
| 912 | lineStart = lineEnd; | |||
| 913 | } | |||
| 914 | } | |||
| 915 | } | |||
| 916 | ||||
| 917 | void WordWrap_GetCoords(int index, const cc_string* lines, int numLines, int* coordX, int* coordY) { | |||
| 918 | int y, offset = 0, lineLen; | |||
| 919 | if (index == -1) index = Int32_MaxValue((cc_int32)2147483647L); | |||
| 920 | *coordX = -1; *coordY = 0; | |||
| 921 | ||||
| 922 | for (y = 0; y < numLines; y++) { | |||
| 923 | lineLen = lines[y].length; | |||
| 924 | if (!lineLen) break; | |||
| 925 | ||||
| 926 | *coordY = y; | |||
| 927 | if (index < offset + lineLen) { | |||
| 928 | *coordX = index - offset; break; | |||
| 929 | } | |||
| 930 | offset += lineLen; | |||
| 931 | } | |||
| 932 | if (*coordX == -1) *coordX = lines[*coordY].length; | |||
| 933 | } | |||
| 934 | ||||
| 935 | int WordWrap_GetBackLength(const cc_string* text, int index) { | |||
| 936 | int start = index; | |||
| 937 | if (index <= 0) return 0; | |||
| 938 | if (index >= text->length) Logger_Abort("WordWrap_GetBackLength - index past end of string"); | |||
| 939 | ||||
| 940 | /* Go backward to the end of the previous word */ | |||
| 941 | while (index > 0 && text->buffer[index] == ' ') index--; | |||
| 942 | /* Go backward to the start of the current word */ | |||
| 943 | while (index > 0 && text->buffer[index] != ' ') index--; | |||
| 944 | ||||
| 945 | return start - index; | |||
| 946 | } | |||
| 947 | ||||
| 948 | int WordWrap_GetForwardLength(const cc_string* text, int index) { | |||
| 949 | int start = index, length = text->length; | |||
| 950 | if (index == -1) return 0; | |||
| 951 | if (index >= text->length) Logger_Abort("WordWrap_GetForwardLength - index past end of string"); | |||
| 952 | ||||
| 953 | /* Go forward to the end of the word 'index' is currently in */ | |||
| 954 | while (index < length && text->buffer[index] != ' ') index++; | |||
| 955 | /* Go forward to the start of the next word after 'index' */ | |||
| 956 | while (index < length && text->buffer[index] == ' ') index++; | |||
| 957 | ||||
| 958 | return index - start; | |||
| 959 | } |