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