Skip to content

Instantly share code, notes, and snippets.

@tinkerer-red
Last active December 3, 2025 06:42
Show Gist options
  • Select an option

  • Save tinkerer-red/c67860e767e7b422aa9dd54ba6e5f645 to your computer and use it in GitHub Desktop.

Select an option

Save tinkerer-red/c67860e767e7b422aa9dd54ba6e5f645 to your computer and use it in GitHub Desktop.
GML : string_split_by_width()
//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;
};
//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;
};
//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;
};
//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