Last active
December 3, 2025 06:42
-
-
Save tinkerer-red/c67860e767e7b422aa9dd54ba6e5f645 to your computer and use it in GitHub Desktop.
GML : string_split_by_width()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //639ms | |
| function string_split_by_width(_str, _line_width, _font=draw_get_font()) { | |
| if (_str == undefined) return; | |
| if (_line_width < 0) _line_width = 10000000; // means nothing will "wrap" | |
| var _prev_font = draw_get_font(); | |
| draw_set_font(_font); | |
| var _whitespace = " "; | |
| var _newline = chr(0x0a); // \n | |
| var _newline2 = chr(0x0d); // \r | |
| var _arr = []; | |
| var _arr_index = 0; | |
| // put newlines in | |
| var _length = string_length(_str); | |
| // Allocate new space | |
| var _new_str = _str; | |
| var lastChar = string_char_at(_new_str, 0); | |
| var _start = 0; | |
| var _end = 0; | |
| while (_start < _length) | |
| { | |
| var total = 0; | |
| // If width < 0 (i.e. no wrapping required), then we DON'T strip spaces from the start... we just copy it! (sounds wrong.. but its what they do...) | |
| if (_line_width == 10000000) | |
| { | |
| while (_end < _length && string_char_at(_new_str, _end) != _newline && string_char_at(_new_str, _end) != _newline2) | |
| { | |
| _end++; | |
| if (_end < _length) lastChar = string_char_at(_new_str, _end); else lastChar = chr(0x0); | |
| } | |
| var c; | |
| if (_end < _length) c = string_char_at(_new_str, _end); else c = chr(0x0); | |
| if ((_newline == lastChar) && (_newline2 == string_char_at(_new_str, _end))) { _end++; continue; } // ignore, we've already split the line on #10 | |
| if ((_newline2 == lastChar) && (_newline == string_char_at(_new_str, _end))) { _end++; continue; } // ignore, we've already split the line on #13 | |
| lastChar = string_char_at(_new_str, _end); | |
| _arr[_arr_index++] = string_copy(_new_str, _start, _end-_start); // add into our list... | |
| } | |
| else | |
| { | |
| // Skip leading whitespace | |
| while (_end < _length && total < _line_width) | |
| { | |
| c = string_char_at(_new_str, _end); | |
| if (string_char_at(_new_str, _end) != _whitespace) break; | |
| total += string_width(c); | |
| _end++; | |
| } | |
| // Loop through string and get the number of chars that will fit in the line. | |
| while (_end < _length && total < _line_width) | |
| { | |
| c = string_char_at(_new_str, _end); | |
| if (c == _newline) break; // if we hit a newline, then "break" here... | |
| total += string_width(c); // add on width of character | |
| _end++; | |
| } | |
| // If we shot past the end, then move back a bit until we fit. | |
| if (total > _line_width) | |
| { | |
| _end--; | |
| total -= string_width(string_char_at(_new_str, _end)); // add on width of character | |
| } | |
| // END of line | |
| if (string_char_at(_new_str, _end) == _newline) | |
| { | |
| //_new_str[_end] = 0x00; | |
| _arr[_arr_index++] = string_copy(_new_str, _start, _end-_start); | |
| } else | |
| { | |
| // NOT a new line, but we didn't move on... fatal error. Probably a single char doesn't even fit! | |
| if (_end == _start) | |
| { | |
| draw_set_font(_prev_font); | |
| return _arr; | |
| } | |
| // If we don't END on a "space", OR if the next character isn't a space AS WELL. | |
| // then backtrack to the start of the last "word" | |
| if (_end != _length) | |
| { | |
| if ((string_char_at(_new_str, _end) != _whitespace) || (string_char_at(_new_str, _end) != _whitespace && string_char_at(_new_str, _end + 1) != _whitespace)) | |
| { | |
| var e = _end; | |
| while (_end > _start) | |
| { | |
| if (string_char_at(_new_str, --e) == _whitespace) break; // FOUND start of word | |
| } | |
| if(e!=_start) | |
| { | |
| _end = e; | |
| } | |
| else { | |
| while(string_char_at(_new_str, _end)!=_whitespace) | |
| _end++; | |
| } | |
| } | |
| } | |
| var __end = _end; | |
| if (__end > _start) | |
| { | |
| while (string_char_at(_new_str, __end - 1) == _whitespace && __end>0) | |
| { | |
| __end--; | |
| } | |
| } | |
| // else if (_end == _start) // if we're back to the START of the string... look for the next space - or string end. | |
| // { | |
| // while (_new_str[_end] != _whitespace && _end < _length) | |
| // { | |
| // _end++; | |
| // } | |
| // } | |
| if(__end!=_start) | |
| _arr[_arr_index++] = string_copy(_new_str, _start, __end-_start); | |
| } | |
| } | |
| _start = ++_end; | |
| } | |
| draw_set_font(_prev_font); | |
| return _arr; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //70ms | |
| function string_split_by_width(_str, line_width, _font=draw_get_font()) { | |
| // means nothing will "wrap" | |
| if (line_width < 0 || line_width == infinity) { | |
| return string_split(_str, "\n"); | |
| } | |
| var _prev_font = draw_get_font(); | |
| draw_set_font(_font); | |
| //unify line breaks | |
| _str = string_replace_all(_str, "\r\n", "\n"); | |
| _str = string_replace_all(_str, "\n\r", "\n"); // just incase... | |
| _str = string_replace_all(_str, "\r", "\n"); | |
| static __closure = {}; | |
| __closure.line_width = line_width; | |
| __closure.arr = []; | |
| __closure.line_str = ""; | |
| __closure.word_str = ""; | |
| __closure.found_non_whitespace = false; | |
| string_foreach(_str, method(__closure, function(_char, _pos) { | |
| // Skip leading whitespace | |
| if (!found_non_whitespace) | |
| && (_char == " ") { | |
| return; | |
| } | |
| found_non_whitespace = true; | |
| // if we hit a newline, then "break" here... | |
| if (_char == "\n" || _char == "\r") { | |
| array_push(arr, line_str + word_str + _char); //include the line break for easy concatination | |
| line_str = ""; | |
| word_str = ""; | |
| return; | |
| } | |
| // if we hit a whitespace, then append word and whitespace | |
| if (_char == " ") { | |
| line_str += word_str + _char; | |
| word_str = ""; | |
| //if the white space is what put us over the line length, keep it on the end and push the line into the array | |
| if (string_width(line_str) > line_width) { | |
| array_push(arr, line_str); | |
| line_str = ""; | |
| } | |
| return; | |
| } | |
| // Loop through string and get the number of chars that will fit in the line. | |
| word_str += _char; | |
| var _length = string_width(line_str + word_str); | |
| if (_length > line_width) { | |
| array_push(arr, line_str); | |
| line_str = word_str; | |
| word_str = ""; | |
| return; | |
| } | |
| })); | |
| //final check | |
| var _length = string_width(__closure.line_str + __closure.word_str); | |
| if (_length > line_width) { | |
| array_push(__closure.arr, __closure.line_str); | |
| array_push(__closure.arr, __closure.word_str); | |
| } | |
| else { | |
| array_push(__closure.arr, __closure.line_str + __closure.word_str); | |
| } | |
| draw_set_font(_prev_font); | |
| return __closure.arr; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //55ms | |
| function string_split_by_width(_str, _width, _font=draw_get_font()) { | |
| // means nothing will "wrap" | |
| if (_width < 0 || _width == infinity) { | |
| return string_split(_str, "\n"); | |
| } | |
| //var _info = font_get_info(_font) | |
| //var _glyphs = _info.glyphs; | |
| var _str_length = string_length(_str); | |
| var _output_arr = array_create(_str_length); | |
| var _char_arr = array_create(_str_length); | |
| var _char_widths = {}; | |
| var _arr_index = 0; | |
| var _line_str = ""; | |
| var _line_width = 0; | |
| var _word_str = ""; | |
| var _word_width = 0; | |
| var _found_non_whitespace = false; | |
| var _append_newline_directly = false; | |
| //__closure.glyphs = _glyphs; | |
| //a really efficient way to map char to an array | |
| static __closure = {}; | |
| __closure.char_arr = _char_arr; | |
| __closure.char_widths = _char_widths; | |
| string_foreach(_str, method(__closure, function(_char, _pos) { | |
| char_arr[_pos] = _char; | |
| if (!struct_exists(char_widths, _char)) { | |
| struct_set(char_widths, _char, string_width(_char)); | |
| } | |
| })); | |
| array_delete(_char_arr, 0, 1); | |
| var _i=0; repeat(_str_length) { | |
| var _char = array_get(_char_arr, _i); | |
| // Skip leading whitespace | |
| if (!_found_non_whitespace) | |
| && (_char == " ") { | |
| _i++ | |
| continue; | |
| } | |
| _found_non_whitespace = true; | |
| // if we hit a newline, then "break" here... | |
| var _append_line = false; | |
| if (_char == "\r") { | |
| _append_newline_directly = true; | |
| _append_line = true; | |
| } | |
| if (_char == "\n") { | |
| if (_append_newline_directly) { | |
| _append_newline_directly = false; | |
| _output_arr[_arr_index-1] += _char; | |
| _i++ | |
| continue; | |
| } | |
| _append_line = true; | |
| } | |
| if (_append_line) { | |
| array_set(_output_arr, _arr_index, string_concat(_line_str, _word_str, _char)); //include the line break for easy concatination | |
| _arr_index++; | |
| _line_str = ""; | |
| _line_width = 0; | |
| _word_str = ""; | |
| _word_width = 0; | |
| _i++ | |
| continue; | |
| } | |
| _append_newline_directly = false; | |
| var _char_width = struct_get(_char_widths, _char); | |
| // if we hit a whitespace, then append word and whitespace | |
| if (_char == " ") { | |
| _line_str = string_concat(_line_str, _word_str, _char); | |
| _line_width += _word_width + _char_width; | |
| _word_str = ""; | |
| _word_width = 0; | |
| //if the white space is what put us over the line length, keep it on the end and push the line into the array | |
| if (_line_width > _width) { | |
| array_set(_output_arr, _arr_index, _line_str); | |
| _arr_index++; | |
| _line_str = ""; | |
| _line_width = 0; | |
| } | |
| _i++ | |
| continue; | |
| } | |
| // Loop through string and get the number of chars that will fit in the line. | |
| _word_str = string_concat(_word_str, _char); | |
| _word_width += _char_width; | |
| var _length = _line_width + _word_width; | |
| if (_length > _width) { | |
| array_set(_output_arr, _arr_index, _line_str); | |
| _arr_index++ | |
| _line_str = _word_str; | |
| _line_width = _word_width; | |
| _word_str = ""; | |
| _word_width = 0; | |
| _i++ | |
| continue; | |
| } | |
| _i++} | |
| //final check | |
| var _length = _line_width + _word_width; | |
| if (_length > _width) { | |
| array_set(_output_arr, _arr_index, _line_str); | |
| _arr_index++; | |
| array_set(_output_arr, _arr_index, _word_str); | |
| } | |
| else { | |
| array_set(_output_arr, _arr_index, string_concat(_line_str, _word_str)); | |
| } | |
| return _output_arr; | |
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //6.7ms | |
| function string_split_by_width(_str, _width, _font = draw_get_font()) { | |
| // means nothing will "wrap" | |
| if (_width < 0 || _width == infinity) { | |
| return string_split(_str, "\n"); | |
| } | |
| var _old_font = draw_get_font(); | |
| if (_font != _old_font) { | |
| draw_set_font(_font); | |
| } | |
| var _space_width = string_width(" "); | |
| var _input_lines = string_split(_str, "\n"); | |
| var _input_line_count = array_length(_input_lines); | |
| var _output_arr = []; | |
| var _line_index = 0; | |
| repeat (_input_line_count) { | |
| var _raw_line = _input_lines[_line_index]; | |
| // Preserve empty lines and short lines. | |
| if (_raw_line == "") | |
| || (string_width(_raw_line) <= _width) { | |
| array_push(_output_arr, _raw_line+"\n"); | |
| _line_index++; | |
| continue; | |
| } | |
| var _words = string_split(_raw_line, " "); | |
| var _word_count = array_length(_words); | |
| var _start_index = 0; | |
| var _segment_word_count = 0; | |
| var _current_width = 0; | |
| var _word_index = 0; | |
| repeat (_word_count) { | |
| var _word = _words[_word_index]; | |
| var _word_width = string_width(_word); | |
| if (_word_width < _width) { | |
| var _new_width = _current_width + _word_width; | |
| if (_new_width < _width) { | |
| _current_width = _new_width + _space_width; | |
| _segment_word_count++; | |
| } | |
| else { | |
| // This word would overflow the current line, flush previous words | |
| if (_segment_word_count > 0) { | |
| array_push( | |
| _output_arr, | |
| string_join_ext(" ", _words, _start_index, _segment_word_count) + " " | |
| ); | |
| _start_index += _segment_word_count; | |
| } | |
| // Start a new line with this word | |
| _current_width = _word_width + ((_word_index+1 < _word_count) ? _space_width : 0); | |
| _segment_word_count = 1; | |
| } | |
| // Width reached or exceeded: flush current line | |
| if (_current_width >= _width) { | |
| array_push( | |
| _output_arr, | |
| string_join_ext(" ", _words, _start_index, _segment_word_count) + " " | |
| ); | |
| _current_width = 0; | |
| _start_index += _segment_word_count; | |
| _segment_word_count = 0; | |
| } | |
| } | |
| else { | |
| // Long word: subdivide into pieces that each fit within _width | |
| // Flush any accumulated shorter words first | |
| if (_segment_word_count > 0) { | |
| array_push( | |
| _output_arr, | |
| string_join_ext(" ", _words, _start_index, _segment_word_count) + " " | |
| ); | |
| _start_index += _segment_word_count; | |
| _segment_word_count = 0; | |
| _current_width = 0; | |
| } | |
| var _word_len = string_length(_word); | |
| var _segment_start_char_index = 1; | |
| var _segment_width_chars = 0; | |
| var _char_index = 1; | |
| repeat (_word_len) { | |
| var _char = string_char_at(_word, _char_index); | |
| var _char_width = string_width(_char); | |
| // If adding this character would overflow the line, flush the segment so far | |
| if (_segment_width_chars > 0 && (_segment_width_chars + _char_width) > _width) { | |
| var _segment_length_chars = _char_index - _segment_start_char_index; | |
| if (_segment_length_chars <= 0) { | |
| // Worst case: single character wider than line, force it alone | |
| array_push(_output_arr, _char); | |
| _segment_start_char_index = _char_index + 1; | |
| _segment_width_chars = 0; | |
| _char_index++; | |
| continue; | |
| } | |
| var _segment_str = string_copy( | |
| _word, | |
| _segment_start_char_index, | |
| _segment_length_chars | |
| ); | |
| array_push(_output_arr, _segment_str); | |
| _segment_start_char_index = _char_index; | |
| _segment_width_chars = 0; | |
| } | |
| _segment_width_chars += _char_width; | |
| _char_index++; | |
| } | |
| // Flush any remaining segment of the long word | |
| if (_segment_width_chars > 0 && _segment_start_char_index <= _word_len) { | |
| var _segment_length_final = (_word_len + 1) - _segment_start_char_index; | |
| var _segment_str_final = string_copy( | |
| _word, | |
| _segment_start_char_index, | |
| _segment_length_final | |
| ); | |
| array_push(_output_arr, _segment_str_final); | |
| } | |
| // Long word is fully consumed; move start index past it | |
| _start_index = _word_index + 1; | |
| _current_width = 0; | |
| _segment_word_count = 0; | |
| } | |
| _word_index++; | |
| } | |
| // Flush any remaining words on this original line | |
| if (_segment_word_count > 0) { | |
| array_push( | |
| _output_arr, | |
| string_join_ext(" ", _words, _start_index, _segment_word_count) + " " | |
| ); | |
| } | |
| _line_index++; | |
| } | |
| if (_font != _old_font) { | |
| draw_set_font(_old_font); | |
| } | |
| return _output_arr; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment