Iam working on a project in Unity3d, using C# scripting, with strings that are written in Hebrew and English.
The problem with Unity3d is that RTL languages (like Hebrew) are not supported when writing UI texts.
The problem looks like this:
original text:
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
(Ground Penetrating Radar) - ראדאר חודר קרקע.
in unity:
GPR -ה תיגולונכט לע תססבתמ רשא ,תכרעמ הניה ףלטעה תכרעמ
.עקרק רדוח ראדאר - (Ground Penetrating Radar)
Iam trying to hack the order that the text is printed to the text boxes for a month now and I cant get it just right.
I made up the following function:
public string CorrectText (string text)
{
string result = "";
string temp = "";
string charListString = "";
List<Char> charList = new List<char> ();
char[] charArray = text.ToCharArray ();
Array.Reverse (charArray);
//go through each char in charArray
for (int x = 0; x <= charArray.Length -1; x++) {
//if the current char we're examing isn't a Space char -
if (!char.IsWhiteSpace (charArray [x])) {
//add it to charList
charList.Add (charArray [x]);
} else { //if the current char we're examing is a Space char -
charListString = new string (charList.ToArray ());
//if charListString doesn't contains English or Numeric chars -
if (!Regex.IsMatch (charListString, "^[0-9a-zA-Z.,()-]*$")) {
//go through each char in charList
for (int y = 0; y <= charList.Count - 1; y++) {
//add the current char to temp as is (not flipped)
temp += charList [y];
}
//add temp to result
if (x < charArray.Length - 1)
result += temp + " ";
if (x == charArray.Length - 1)
result += temp;
//clear charList and temp
charList.Clear ();
temp = "";
} else { //if temp contains English or Numeric chars -
//go through each char in charList - This flipps the order of letters
for (int y = charList.Count - 1; y >= 0; y--) {
//add the current char to temp
temp += charList [y];
}
//add temp to result
if (x < charArray.Length - 1)
result += temp + " ";
if (x == charArray.Length - 1)
result += temp;
//clear charList and temp
charList.Clear ();
temp = "";
}
}
}
return result;
}
this almost fixed the problem, but the text is written from bottom up for example:
input:
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
(Ground Penetrating Radar) - ראדאר חודר קרקע.
output:
(Ground Penetrating Radar) - ראדאר חודר קרקע.
מערכת העטלף הינה מערכת, אשר מתבססת על טכנולוגית ה- GPR
and sometimes the parenthesis are getting mixed up in the sentence, showing like this:
)Ground Penetrating( Radar - ראדאר חודר קרקע.
I've found a tool online that turns Visual Hebrew to Logic Hebrew, and when I copied the flipped text to unity it worked like magic!
But I cant find any useful information online on how to make my own script with C# that does the same thing.
I managed to overcome this... sort of:
Assign the string into the text element.
Force the canvas to update using Canvas.ForceUpdateCanvases(), or another means.
Get the cached text generator (NOT for layout) of the text component. It will lie to you if you didn't force an update or yielded and waited for the next frame; so don't skip step 2.
Use getLines of the cached text generator to get the information of the text wrapping; it holds the index where every line starts.
Take the maximum difference in the values of those and you have the likely amount of characters per row before it wraps.
Insert '\n' (new line) into the original string manually using the row length you found in the previous step before flipping the characters!
Do the reversal process on the wrapped string. If you do these things in a different order you will end up with the first line being the shortest, instead of the last. So wrap first, reverse second.
Set the string to the text component and it should work. Usually.
Related
I got an idea that i wanted to make today but ran into the problem that i have a string variable with paragraph, but i need an Array of single lines. So I tried to do this with the String.Substring and the String.IndexOf functions but this only worked kinda because i dont exactly know how VisualStudio handels the Index of Paragraphs and how strings work with paragraphs because i just learned C# this year.
I tried it in Windows-Forms btw.
Can anyone tell me how index's with paragraphs work or especialy how to use them correctly.
This is the code i tried which only works for the 1st line and works kinda with the 2nd but not with any further
string input_raw;
string[] input = new string[100];
int index_zeile = 0;
int x = 0, y = 0;
input_raw = input_text_box.Text;
for (int i = 0; i < 100; i++)
{
if (i == 0)
{
y = input_raw.IndexOf(";");
input[i] = input_raw.Substring(index_zeile, y);
x = y + 1;
}
else
{
index_zeile = input_raw.IndexOf(";", x);
input[i] = input_raw.Substring(x, index_zeile-3);
x = x + index_zeile;
}
}
I wish you entered your text input, but you can split the string into an array. The following code assigns a multi-line string to an array.
string[] lines = input_text_box.Text.Split('\n');
If the searched word is at the beginning or end of the line of, I want to color it. If it's in the middle of the line not colorful. I tried many thing but it's not working right.
Seems like beginning of the line is working. But end of the first line only can color. I want to color for all lines end. I think i need index of each line's beggining and loop but i can't do it.
How can i fix it?
private void button1_Click(object sender, EventArgs e)
{
int wordLength = textBox1.Text.Length;
string word = textBox1.Text;
for (int i = 0; i < richTextBox1.Lines.Count(); i++)
{
int startIndex = richTextBox1.GetFirstCharIndexFromLine(i);
richTextBox1.Find(word, startIndex, startIndex+wordLength, RichTextBoxFinds.None);
richTextBox1.SelectionColor = Color.Red;
richTextBox1.SelectionBackColor = Color.Yellow;
int newLineIndex = richTextBox1.Lines[i].Length;
richTextBox1.Find(textBox1.Text, (newLineIndex - wordLength), newLineIndex, RichTextBoxFinds.None);
richTextBox1.SelectionColor = Color.Red;
richTextBox1.SelectionBackColor = Color.Yellow;
}
Try:
int newLineIndex = i + 1 < richTextBox1.Lines.Length ? richTextBox1.GetFirstCharIndexFromLine(i + 1) - 1 : richTextBox1.TextLength;
I suggest to change your code a little bit. You'll notice why when the RichTextBox text length grows.
Asking for the Lines[] content is not exactly a good thing, much worse in a loop, when you access this Property probably many of times.
You can see in the .Net Source Code what happens (each time - the Lines Property values are not cached and cannot be).
GetLineFromCharIndex() and GetFirstCharIndexFromLine() use instead SendMessage to send the EM_LINEFROMCHAR and EM_LINEINDEX messages to the Edit control - which uses cached values - and are pretty fast.
Use Regex.Matches() to collect the indexes of the matched word(s) (you can use more than one word, separated by a pipe: "|", but here we handle just one word. When matching more than one word, use a List<Match> and the Match.Length instead of searchWord.Length) and extract just the Index position of each match.
Then, loop the indexes and check whether the current index position meets the criteria.
The current line end is found with IndexOf("\n", [StartPosition]), using the first line index (which is also used for the selection) as the starting position.
The RichTextBox Control uses only \n as line separator, so we don't need to worry about \r.
string searchWord = "John";
var txt = richTextBox1.Text;
int textLenght = txt.Length;
// the indexes list can be created with the alternative method (using IndexOf() in a loop)
var indexes = Regex.Matches(txt, searchWord, RegexOptions.Multiline)
.OfType<Match>()
.Select(m => m.Index).ToList();
foreach (int index in indexes) {
int currentLine = richTextBox1.GetLineFromCharIndex(index);
int lineFirstIndex = richTextBox1.GetFirstCharIndexFromLine(currentLine);
int lineLastIndex = txt.IndexOf("\n", lineFirstIndex);
if (index == lineFirstIndex ||
index == lineLastIndex - searchWord.Length ||
index == textLenght - searchWord.Length) {
richTextBox1.Select(index, searchWord.Length);
richTextBox1.SelectionColor = Color.Red;
}
}
Edit: Since Regex.Matches is not allowed, you can use IndexOf() in a loop:
var indexes = new List<int>();
int wordPosition = -1;
do {
if ((wordPosition = txt.IndexOf(searchWord, wordPosition + 1)) >= 0) {
indexes.Add(wordPosition);
}
} while (wordPosition >= 0);
To illustrate:
Take the input string "Yesterday I ate two bu rgers" (space intentional)
I want to check the input string to see if a space (or any other pre-defined character) " " exists between the two characters (in this case) "u" and "r". And if it exists delete this character.
First I came up with this:
string someString = "Yesterday I ate two bu rgers";
string charA = 'u', charB = 'r';
if (someString.Contains(charA) &&
someString.Substring(someString.IndexOf(charA) + 1).Equals(" ") &&
someString.Substring(someString.IndexOf(charA) + 2).Equals(charB))
//delete the space
However not only does this feel (and look) inefficient as heck, It also fails if the sentence would be "Yesterday you ate two bu rgers" since it will take the index of the first "u". So I would have to do an additional check for multiple instances of charA
Another solution I thought of is to split the sentence on every space, and see if the last character of the split matches charA and the first character of the next split matches charB. And if it does join the two together.
string[] splitString = someString.Split(null);
for (int i = 0; i < splitString.Length -1; i++)
{
string lastChar = splitString[i].Substring(splitString[i].Length - 1);
string firstChar = splitString[i + 1].Substring(0, 1);
if(lastChar.Equals(charA) && firstChar.Equals(charB))
{
string joined = splitString[i] + splitString[i + 1];
}
}
However this method is also flawed as it breaks when i.e two spaces are present in the input.
Is there a way to do this without needing a bunch of if statements or loops? (unless there really is no other way I would really like to not use regex)
A string is an array of characters. Loop through it and inspect the characters.
for (int i = 2; i < someString.Length; i++) {
if (someString[i] == charB && someString[i - 2] == charA) {
//TODO: delete the char in between.
break;
}
}
If you start at index = 2 and test for the second character, you can simply go back by 2 positions to inspect the first one.
But of course you could also look ahead like this:
for (int i = 0; i < someString.Length - 2; i++) {
if (someString[i] == charA && someString[i + 2] == charB) {
//TODO: delete the char in between.
break;
}
}
I am having a problem whereby the letter at the position(e.g 39) would be replaced with the text I wanted to input. However what I want was to insert the text at position 39 instead of replacing it. Anyone please guide me on this.
string description = variables[1]["value"].ToString();// where I get the text
int nInterval = 39;// for every 39 characters in the text I would have a newline
string res = String.Concat(description.Select((c, z) => z > 0 && (z % nInterval) == 0 ? Environment.NewLine +"Hello"+(((z/ nInterval)*18)+83).ToString()+"world": c.ToString()));
file_lines = file_lines.Replace("<<<terms_conditions>>>",resterms); //file_lines is where I read the text file
Original text
Present this redemption slip to receive: One
After String.Concat
Present this redemption slip to receive\r\n\u001bHello101world
One //: is gone
I am also having a issue where I want to put a new line if it contains * in the text. If anybody is able to help that would be great.
Edit:
What I want to achieve is something like this
Input
*Item is considered paid if unsealed.*No replacement or compensation will be given for any expired coupons.
so like i need to find every 39 character and also * to input newline so it will be
Output
*Item is considered paid if unsealed.
*No replacement or compensation will be
given for any expired coupons.
Try String.Insert(Int32, String) Method
Insert \n where you need new line.
If I understood your question properly, you want a newline after every 39 characters. You can use string.Insert(Int32, String) method for that.
And use String.Replace(String, String) for your * problem.
Below code snippet doing that using a simple for loop.
string sampleStr = "Lorem Ipsum* is simply..";
for (int i = 39; i < sampleStr.Length; i = i + 39){
sampleStr = sampleStr.Insert(i, Environment.NewLine);
}
//sampleStr = sampleStr.Replace("*", Environment.NewLine);
int[] indexes = Enumerable.Range(0, sampleStr.Length).Where(x => sampleStr[x] == '*').ToArray();
for (int i = 0; i < indexes.Length; i++)
{
int position = indexes[i];
if (position > 0) sampleStr = sampleStr.Insert(position, Environment.NewLine);
}
If you want to do both together
int[] indexes = Enumerable.Range(0, sampleStr.Length).Where(x => sampleStr[x] == '*' || x % 39 == 0).ToArray();
int j = 0;
foreach (var position in indexes)
{
if (position > 0)
{
sampleStr = sampleStr.Insert(position + j, Environment.NewLine);
j = j + 2; // increment by two since newline will take two chars
}
}
Without debating the method chosen to achieve the desired result, the problem with the code is that at the 39th character it adds some text, but the character itself has been forgotten.
Changing the following line should give the expected output.
string res = String.Concat(description.Select((c, z) => z > 0 && (z % nInterval) == 0 ? Environment.NewLine + "Hello" + (((z / nInterval) * 18) + 83).ToString() + "world" + c.ToString() : c.ToString()));
<== UPDATED ANSWER BASED ON CLARIFICATION IN QUESTION ==>
This will do what you want, I believe. See comments in line.
var description = "*Item is considered paid if unsealed.*No replacement or compensation will be given for any expired coupons.";
var nInterval = 39; // for every 39 characters in the text I would have a newline
var newline = "\r\n"; // for clarity in the Linq statement. Can be set to Environment.Newline if desired.
var z = 0; // we'll handle the count manually.
var res = string.Concat(
description.Select(
(c) => (++z == nInterval || c == '*') // increment z and check if we've hit the boundary OR if we've hit a *
&& ((z = 0)==0) // resetting the count - this only happens if the first condition was true
? newline + (c == ' ' ? string.Empty : c.ToString()) // if the first character of a newline is a space, we don't need it
: c.ToString()
));
Output:
*Item is considered paid if unsealed.
*No replacement or compensation will be
given for any expired coupons.
Hi guys, so I need to add a 'space' between each character in my displayed text box.
I am giving the user a masked word like this He__o for him to guess and I want to convert this to H e _ _ o
I am using the following code to randomly replace characters with '_'
char[] partialWord = word.ToCharArray();
int numberOfCharsToHide = word.Length / 2; //divide word length by 2 to get chars to hide
Random randomNumberGenerator = new Random(); //generate rand number
HashSet<int> maskedIndices = new HashSet<int>(); //This is to make sure that I select unique indices to hide. Hashset helps in achieving this
for (int i = 0; i < numberOfCharsToHide; i++) //counter until it reaches words to hide
{
int rIndex = randomNumberGenerator.Next(0, word.Length); //init rindex
while (!maskedIndices.Add(rIndex))
{
rIndex = randomNumberGenerator.Next(0, word.Length); //This is to make sure that I select unique indices to hide. Hashset helps in achieving this
}
partialWord[rIndex] = '_'; //replace with _
}
return new string(partialWord);
I have tried : partialWord[rIndex] = '_ ';however this brings the error "Too many characters in literal"
I have tried : partialWord[rIndex] = "_ "; however this returns the error " Cannot convert type string to char.
Any idea how I can proceed to achieve a space between each character?
Thanks
The following code should do as you ask. I think the code is pretty self explanatory., but feel free to ask if anything is unclear as to the why or how of the code.
// char[] partialWord is used from question code
char[] result = new char[(partialWord.Length * 2) - 1];
for(int i = 0; i < result.Length; i++)
{
result[i] = i % 2 == 0 ? partialWord[i / 2] : ' ';
}
return new string(result);
Since the resulting string is longer than the original string, you can't use only one char array because its length is constant.
Here's a solution with StringBuilder:
var builder = new StringBuilder(word);
for (int i = 0 ; i < word.Length ; i++) {
builder.Insert(i * 2, " ");
}
return builder.ToString().TrimStart(' '); // TrimStart is called here to remove the leading whitespace. If you want to keep it, delete the call.