Adding newline character to string after a specified amount of characters

N

Nabil Kabour

Guest
Hi,

I am trying to make a script which inputs a string and returns that string with newline characters every n amount of characters. However I do not want a newline character to be added in the middle of a word or number, and I do not want a newline character to be added after or before another newline character. After some time I have come up with this script:

Code:
/// @description Adds new line every n amount of characters
/// @arg string
/// @arg n

var str_length = string_length(argument0);
var times_to_newline = str_length div argument1;
var formatted_text = argument0;

for(var i = 1; i <= times_to_newline; i++)
{   
    var j = 0;
    while(string_char_at(formatted_text, i*argument1 - j) != " " )
    {
        j--;
    }
    formatted_text = string_insert("\n",formatted_text,i * argument1 - j);
}

return formatted_text;
It does the job of making each line contain less than n amount of characters, but it does not fill out each line sufficiently. Honestly I am stumped at this point and any help is greatly appreciated. Thanks.
 
D

dannyjenn

Guest
I am not sure what the problem is with your code, though part of the problem might be with your j--. (I could be wrong, but I think it should be j++.)

Anyway, I've written some code using an entirely different approach. Sorry if this code is confusing... I tried to comment it with explanations, but I still probably wasn't very clear. The overall idea is to break the string into n-character-long substrings (marking the beginning of the substring with first and the end of it with last). We first place the 'last' exactly n characters after 'first', but if that's in the middle of a word then we move it back one character (i.e. one character closer to first). We check again, and repeat that until it is no longer in the middle of the word. When we have found the correct position, we then take the substring that's between 'first' and 'last' and we copy it onto our output string. Then we place the newline character where it goes. Then we update our 'first' to the beginning of the next substring, and we update our 'last' to n characters after that, and we repeat the whole process, until we are past the end of the string, in which case we return the output:
Code:
var str_length = string_length(argument0);
var formatted_text = "";
var first = 1; // first marks the beginning of the current substring
var last = 1; // last marks the end of the current substring--the position at which we want to place the newline
while(last<=str_length){ // loop until we get to the end of the string
    last += argument1; // increase the 'last' marker by n characters
    if(last>str_length){ // if we've gone past the end of the string, we're done
        formatted_text += string_copy(argument0,first,last-first); // put the final substring onto the output
        return formatted_text; // return the output
    }
    while(string_char_at(argument0,last)!=" " && last>first){ // loop while the 'last' marker is in the middle of a word or number
        last -= 1; // move the 'last' marker back one character, until it is no longer in the middle of a word/number, or until it reaches the 'first' marker
    }
    if(last==first){ // if the 'last' marker is at the same position as the 'first' marker
        // error: there is some word or number which is longer than n-characters, so there's place to add the newline except in the middle of that word/number
        // the following code inserts the newline into the middle of that word anyway:
        last += argument1; // we move the 'last' marker forward by n characters
        formatted_text += string_copy(argument0,first,last-first) + "\n"; // we add the current substring to our output, and we also add a newline character
        last -= 1; // <-- this line is needed to cancel out the last+=1 which shows up a few lines later
    }
    else{
        formatted_text += string_copy(argument0,first,last-first) + "\n"; // add the current substring to the output, and add the newline to it
    }
    last += 1; // this line deletes the space where the newline is added
    first = last; // move on to the next substring
}
 
Last edited by a moderator:
N

Nabil Kabour

Guest
I am not sure what the problem is with your code, though part of the problem might be with your j--. (I could be wrong, but I think it should be j++.)

Anyway, I've written some code using an entirely different approach. Sorry if this code is confusing... I tried to comment it with explanations, but I still probably wasn't very clear. The overall idea is to break the string into n-character-long substrings (marking the beginning of the substring with first and the end of it with last). We first place the 'last' exactly n characters after 'first', but if that's in the middle of a word then we move it back one character (i.e. one character closer to first). We check again, and repeat that until it is no longer in the middle of the word. When we have found the correct position, we then take the substring that's between 'first' and 'last' and we copy it onto our output string. Then we place the newline character where it goes. Then we update our 'first' to the beginning of the next substring, and we update our 'last' to n characters after that, and we repeat the whole process, until we are past the end of the string, in which case we return the output:
Code:
var str_length = string_length(argument0);
var formatted_text = "";
var first = 1; // first marks the beginning of the current substring
var last = 1; // last marks the end of the current substring--the position at which we want to place the newline
while(last<=str_length){ // loop until we get to the end of the string
    last += argument1; // increase the 'last' marker by n characters
    if(last>str_length){ // if we've gone past the end of the string, we're done
        formatted_text += string_copy(argument0,first,last-first); // put the final substring onto the output
        return formatted_text; // return the output
    }
    while(string_char_at(argument0,last)!=" " && last>first){ // loop while the 'last' marker is in the middle of a word or number
        last -= 1; // move the 'last' marker back one character, until it is no longer in the middle of a word/number, or until it reaches the 'first' marker
    }
    if(last==first){ // if the 'last' marker is at the same position as the 'first' marker
        // error: there is some word or number which is longer than n-characters, so there's place to add the newline except in the middle of that word/number
        // the following code inserts the newline into the middle of that word anyway:
        last += argument1; // we move the 'last' marker forward by n characters
        formatted_text += string_copy(argument0,first,last-first) + "\n"; // we add the current substring to our output, and we also add a newline character
        last -= 1; // <-- this line is needed to cancel out the last+=1 which shows up a few lines later
    }
    else{
        formatted_text += string_copy(argument0,first,last-first) + "\n"; // add the current substring to the output, and add the newline to it
    }
    last += 1; // this line deletes the space where the newline is added
    first = last; // move on to the next substring
}
Hey thanks for your reply and taking the time to write some code. Your code seems to have the same functionality as mine. I've figured out the issue, it seems to me the issue is that the string I'm inputting to be formatted may already contain newline characters, and after passing the string to be formatted we end up with more newline characters than needed. For example: I am passing this string to the function
Code:
var txt = name + "\n" + description + "\n " + "Sell price: " + string(item_sell_price);
txt = text_add_new_line(txt,26);
where text_add_new_line is the function that is the code that we've written. I guess we need to check if there is already a newline character, and not add a new one at least n amount of characters after it. I'll try to adjust my code to see if I can do it, and I'll reply to this thread if I succeed.
 
N

Nabil Kabour

Guest
EDIT: Actually I don't think the code is performing the intended action, but I'll keep this post for reference.

So I managed to get it working, here is the code:
Code:
/// @description Adds new line every n amount of characters
/// @arg string
/// @arg n


var str_length = string_length(argument0);
var chars_per_line  = argument1;
var times_to_newline = str_length div chars_per_line;
var formatted_text = argument0;
var str_length  = string_length(formatted_text);


var i;
var h;

var insert_position;
var original_text = string_copy(formatted_text, 1, str_length);

for(i = 1; i <= times_to_newline; i++)
{  
    for(h = 1; h <= chars_per_line; h++)
    {
        var j = 0;
        var char_position =  i * h;
        var char = string_char_at(original_text, i * h);
        if(char == "\n")
        {
            break;
        }
        else if(h == chars_per_line)
        {
            while(string_char_at(original_text, char_position - j) != " ")
            {
                j--;
            }
            insert_position = char_position - j;
            formatted_text = string_delete(formatted_text, insert_position, 1);
            formatted_text = string_insert("\n" , formatted_text, insert_position);
        }
       
    }
}

return formatted_text;
Basically it never allows a single line to contain more than n+1 (I don't know why that is, it should be n) characters. It also takes into account if the string already contains newline characters, and it does not break up words or numbers. However there does seem to be a problem with the script, it sometimes hangs when passing certain strings under certain n values. But under normal use it seems to be working fine. I'll update if I find a solution to this or if this problem becomes an issue.
 
D

dannyjenn

Guest
I see a few big problems in your code.

1.) Input:
Code:
argument0 = "The quick\nbrown fox jumps over the lazy dog.";
argument1 = 10;
Desired output:
Code:
The quick
brown fox
jumps over
the lazy
dog.
Actual output:
Code:
The quick
brown fox jumps over
the lazy
dog.
I believe the problem here is due to that break statement in your script. You break on the newline, which causes i to increment (i.e. you basically jump ahead to the next set of n characters) before your h begins counting again. That means that in the worst case you could end up with a line that's almost twice as long as it should be. Unfortunately, I don't think that there's an easy way you can fix this without completely re-writing the structure of those loops.

2.) Input:
Code:
argument0 = "The quick brown fox jumps over the lazy dog.";
argument1 = 13;
Desired output:
Code:
The quick
brown fox
jumps over
the lazy dog.
Actual output:
Code:
The quick brown
fox jumps
over the lazy
dog.
There are two problems here. First is that when your h loop ends in the middle of a word, it then goes to the end of that word (since you did j--). I'm not sure why you did it like that, since that's always going to cause the line length to exceed n characters. What you want is for it to go to the beginning of the word (by using j++). However, the second problem is that your i loop then jumps ahead to the next iteration and then the h loop begins counting, basically ignoring the rest of the characters that were in that last word.


Overall, I would say that you should restructure your loops. I would change it to something like this:
Code:
for(i=1;i<=str_length;i+=chars_per_line){
    for(h=0;h<chars_per_line;h+=1){
        // . . .
    }
}
(Though you'd then need to change char_position to i+h rather than i*h, and you'd probably need to change some other things as well.)
By restructuring your loop, you could then have more control over where h begins counting from.


Then there are the following issues, which are less problematic though still something you should address:

3.) Input:
Code:
argument0 = "The quick brown fox jumps over the lazy dog.";
argument1 = 2;
Desired output: Depends on what you need this script to do.
There's no valid output (i.e. nothing that satisfies both the rule concerning line length and the rule against newlines in the middle of words). So you might want to output an error message
Code:
ERROR
Or maybe just an empty string
Code:
Or you might instead want to output some "good" invalid output, either:
Code:
The
quick
brown
fox
jumps
over
the
lazy
dog.
(preserves the words from being broken in the middle)
or
Code:
Th
e
qu
ic
k
br
ow
n
fo
x
ju
mp
s
ov
er
th
e
la
zy
do
g.
(preserves the 2-character limit)
Actual output: none. Your script currently just freezes. This is bad. You should definitely have it output something. Otherwise you'll need to be very careful what input you give it.

4.) Input:
Code:
argument0 = "The quickbrownfox jumpsoverthelazy dog.";
argument1 = 10;
Desired output: Again, depends on what you're using the script for.
N
o valid output for this, since no matter where you place the newline you'll either need to break the rule against newlines in the middle of words or else you'll need to have some lines of text which exceed the line limit. So perhaps an error message, or an empty string, or some of the "good" invalid output. Either
Code:
The
quickbrownfox
jumpsoverthelazy
dog.
(preserves the words from linebreaks in the middle)
or
Code:
The
quickbrown
fox
jumpsovert
helazy
dog.
(keeps the lines at or under 10 characters)
Actual output:
Code:
The quickbrownfox
jumpsoverthelazy
dog.
This is better than the script just freezing, but you'd think there should be a newline after the "The".


Apart from that, there's a few small things I notice:

Code:
var original_text = string_copy(formatted_text, 1, str_length);
In case you're not aware, this is unnecessary. You can just do
Code:
var original_text = formatted_text;
or
Code:
var original_text = argument0;
(When you assign a string value to a variable, GameMaker automatically makes of copy of that string. You'd only need to use string_copy() if you wanted to get a substring, not the whole string.)

And
Code:
var char = string_char_at(original_text, i * h);
Change that to
Code:
var char = string_char_at(original_text, char_position);
(Both do the same thing, but the second way is better.)
 
Last edited by a moderator:
N

Nabil Kabour

Guest
I see a few big problems in your code.

1.) Input:
Code:
argument0 = "The quick\nbrown fox jumps over the lazy dog.";
argument1 = 10;
Desired output:
Code:
The quick
brown fox
jumps over
the lazy
dog.
Actual output:
Code:
The quick
brown fox jumps over
the lazy
dog.
I believe the problem here is due to that break statement in your script. You break on the newline, which causes i to increment (i.e. you basically jump ahead to the next set of n characters) before your h begins counting again. That means that in the worst case you could end up with a line that's almost twice as long as it should be. Unfortunately, I don't think that there's an easy way you can fix this without completely re-writing the structure of those loops.

2.) Input:
Code:
argument0 = "The quick brown fox jumps over the lazy dog.";
argument1 = 13;
Desired output:
Code:
The quick
brown fox
jumps over
the lazy dog.
Actual output:
Code:
The quick brown
fox jumps
over the lazy
dog.
There are two problems here. First is that when your h loop ends in the middle of a word, it then goes to the end of that word (since you did j--). I'm not sure why you did it like that, since that's always going to cause the line length to exceed n characters. What you want is for it to go to the beginning of the word (by using j++). However, the second problem is that your i loop then jumps ahead to the next iteration and then the h loop begins counting, basically ignoring the rest of the characters that were in that last word.


Overall, I would say that you should restructure your loops. I would change it to something like this:
Code:
for(i=1;i<=str_length;i+=chars_per_line){
    for(h=0;h<chars_per_line;h+=1){
        // . . .
    }
}
(Though you'd then need to change char_position to i+h rather than i*h, and you'd probably need to change some other things as well.)
By restructuring your loop, you could then have more control over where h begins counting from.


Then there are the following issues, which are less problematic though still something you should address:

3.) Input:
Code:
argument0 = "The quick brown fox jumps over the lazy dog.";
argument1 = 2;
Desired output: Depends on what you need this script to do.
There's no valid output (i.e. nothing that satisfies both the rule concerning line length and the rule against newlines in the middle of words). So you might want to output an error message
Code:
ERROR
Or maybe just an empty string
Code:
Or you might instead want to output some "good" invalid output, either:
Code:
The
quick
brown
fox
jumps
over
the
lazy
dog.
(preserves the words from being broken in the middle)
or
Code:
Th
e
qu
ic
k
br
ow
n
fo
x
ju
mp
s
ov
er
th
e
la
zy
do
g.
(preserves the 2-character limit)
Actual output: none. Your script currently just freezes. This is bad. You should definitely have it output something. Otherwise you'll need to be very careful what input you give it.

4.) Input:
Code:
argument0 = "The quickbrownfox jumpsoverthelazy dog.";
argument1 = 10;
Desired output: Again, depends on what you're using the script for.
N
o valid output for this, since no matter where you place the newline you'll either need to break the rule against newlines in the middle of words or else you'll need to have some lines of text which exceed the line limit. So perhaps an error message, or an empty string, or some of the "good" invalid output. Either
Code:
The
quickbrownfox
jumpsoverthelazy
dog.
(preserves the words from linebreaks in the middle)
or
Code:
The
quickbrown
fox
jumpsovert
helazy
dog.
(keeps the lines at or under 10 characters)
Actual output:
Code:
The quickbrownfox
jumpsoverthelazy
dog.
This is better than the script just freezing, but you'd think there should be a newline after the "The".


Apart from that, there's a few small things I notice:

Code:
var original_text = string_copy(formatted_text, 1, str_length);
In case you're not aware, this is unnecessary. You can just do
Code:
var original_text = formatted_text;
or
Code:
var original_text = argument0;
(When you assign a string value to a variable, GameMaker automatically makes of copy of that string. You'd only need to use string_copy() if you wanted to get a substring, not the whole string.)

And
Code:
var char = string_char_at(original_text, i * h);
Change that to
Code:
var char = string_char_at(original_text, char_position);
(Both do the same thing, but the second way is better.)
Thank you for your reply. I am not sure how to proceed from here. The two issues where the script freezes and where it returns the wrong output (the bit where argument1 = 2 and argument1 = 10) aren't too problematic as I will never provide such a string to the script with those conditions. I have restructured the loop as you advised, but I end up with one line of text. The problem I'm having is visualizing what the script does. It's late at night now and I can barely think, I'll give it another shot tomorrow. I really appreciate your help and would be thankful if you could provide me with more guidance if you have the time.
 
Top