/// <summary> /// Fixes the flow of the text. /// </summary> public static void Fix(FastStringBuilder input, FastStringBuilder output, bool farsi, bool fixTextTags, bool preserveNumbers) { // Some texts like tags, English words and numbers need to be displayed in their original order. // This list keeps the characters that their order should be reserved and streams reserved texts into final letters. LtrTextHolder.Clear(); TagTextHolder.Clear(); for (int i = input.Length - 1; i >= 0; i--) { bool isInMiddle = i > 0 && i < input.Length - 1; bool isAtBeginning = i == 0; bool isAtEnd = i == input.Length - 1; char characterAtThisIndex = input.Get(i); char nextCharacter = default; if (!isAtEnd) { nextCharacter = input.Get(i + 1); } char previousCharacter = default; if (!isAtBeginning) { previousCharacter = input.Get(i - 1); } if (fixTextTags) { if (characterAtThisIndex == '>') { // We need to check if it is actually the beginning of a tag. bool isValidTag = false; int nextI = i; TagTextHolder.Add(characterAtThisIndex); for (int j = i - 1; j >= 0; j--) { var jChar = input.Get(j); // Tags do not have space inside if (jChar == ' ') { break; } // Tags do not have RTL characters inside if (TextUtils.IsRTLCharacter(jChar)) { break; } TagTextHolder.Add(jChar); if (jChar == '<') { isValidTag = true; nextI = j; break; } } if (isValidTag) { FlushBufferToOutput(LtrTextHolder, output); FlushBufferToOutput(TagTextHolder, output); i = nextI; continue; } else { TagTextHolder.Clear(); } } } if (char.IsPunctuation(characterAtThisIndex) || char.IsSymbol(characterAtThisIndex)) { if (MirroredCharsSet.Contains(characterAtThisIndex)) { // IsRTLCharacter returns false for null bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); if (isAfterRTLCharacter || isBeforeRTLCharacter) { characterAtThisIndex = MirroredCharsMap[characterAtThisIndex]; } } if (isInMiddle) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); bool isBeforeWhiteSpace = char.IsWhiteSpace(nextCharacter); bool isAfterWhiteSpace = char.IsWhiteSpace(previousCharacter); bool isUnderline = characterAtThisIndex == '_'; bool isSpecialPunctuation = characterAtThisIndex == '.' || characterAtThisIndex == '،' || characterAtThisIndex == '؛'; if (isBeforeRTLCharacter && isAfterRTLCharacter || isAfterWhiteSpace && isSpecialPunctuation || isBeforeWhiteSpace && isAfterRTLCharacter || isBeforeRTLCharacter && isAfterWhiteSpace || (isBeforeRTLCharacter || isAfterRTLCharacter) && isUnderline) { FlushBufferToOutput(LtrTextHolder, output); output.Append(characterAtThisIndex); } else { LtrTextHolder.Add(characterAtThisIndex); } } else if (isAtEnd) { LtrTextHolder.Add(characterAtThisIndex); } else if (isAtBeginning) { output.Append(characterAtThisIndex); } continue; } if (isInMiddle) { bool isAfterEnglishChar = TextUtils.IsEnglishLetter(previousCharacter); bool isBeforeEnglishChar = TextUtils.IsEnglishLetter(nextCharacter); bool isAfterNumber = TextUtils.IsNumber(previousCharacter, preserveNumbers, farsi); bool isBeforeNumber = TextUtils.IsNumber(nextCharacter, preserveNumbers, farsi); bool isAfterSymbol = char.IsSymbol(previousCharacter); bool isBeforeSymbol = char.IsSymbol(nextCharacter); // For cases where english words and farsi/arabic are mixed. This allows for using farsi/arabic, english and numbers in one sentence. // If the space is between numbers,symbols or English words, keep the order if (characterAtThisIndex == ' ' && (isBeforeEnglishChar || isBeforeNumber || isBeforeSymbol) && (isAfterEnglishChar || isAfterNumber || isAfterSymbol)) { LtrTextHolder.Add(characterAtThisIndex); continue; } } if (TextUtils.IsEnglishLetter(characterAtThisIndex) || TextUtils.IsNumber(characterAtThisIndex, preserveNumbers, farsi)) { LtrTextHolder.Add(characterAtThisIndex); continue; } if (characterAtThisIndex >= (char)0xD800 && characterAtThisIndex <= (char)0xDBFF || characterAtThisIndex >= (char)0xDC00 && characterAtThisIndex <= (char)0xDFFF) { LtrTextHolder.Add(characterAtThisIndex); continue; } FlushBufferToOutput(LtrTextHolder, output); if (characterAtThisIndex != 0xFFFF && characterAtThisIndex != (int)GeneralLetters.ZeroWidthNoJoiner) { output.Append(characterAtThisIndex); } } FlushBufferToOutput(LtrTextHolder, output); }
/// <summary> /// Fixes the shape of letters based on their position. /// </summary> /// <param name="input"></param> /// <param name="output"></param> /// <param name="preserveNumbers"></param> /// <param name="farsi"></param> /// <returns></returns> public static void Fix(FastStringBuilder input, FastStringBuilder output, bool preserveNumbers, bool farsi, bool fixTextTags) { FixYah(input, farsi); output.SetValue(input); for (int i = 0; i < input.Length; i++) { bool skipNext = false; int iChar = input.Get(i); // For special Lam Letter connections. if (iChar == (int)ArabicGeneralLetters.Lam) { if (i < input.Length - 1) { skipNext = HandleSpecialLam(input, output, i); if (skipNext) { iChar = output.Get(i); } } } // We don't want to fix tatweel or zwnj character if (iChar == (int)ArabicGeneralLetters.Tatweel || iChar == (int)SpecialCharacters.ZeroWidthNoJoiner) { continue; } if (iChar < 0xFFFF && TextUtils.IsGlyphFixedArabicCharacter((char)iChar)) { char converted = GlyphTable.Convert((char)iChar); if (IsMiddleLetter(input, i)) { output.Set(i, (char)(converted + 3)); } else if (IsFinishingLetter(input, i)) { output.Set(i, (char)(converted + 1)); } else if (IsLeadingLetter(input, i)) { output.Set(i, (char)(converted + 2)); } else { output.Set(i, (char)converted); } } // If this letter as Lam and special Lam-Alef connection was made, We want to skip the Alef // (Lam-Alef occupies 1 space) if (skipNext) { i++; } } if (!preserveNumbers) { if (fixTextTags) { FixNumbersOutsideOfTags(output, farsi); } else { FixNumbers(output, farsi); } } }
/// <summary> /// Is the letter at provided index a leading letter? /// </summary> /// <returns><see langword="true" /> if the letter is a leading letter</returns> private static bool IsLeadingLetter(FastStringBuilder letters, int index) { var currentIndexLetter = letters.Get(index); int previousIndexLetter = default; if (index != 0) { previousIndexLetter = letters.Get(index - 1); } int nextIndexLetter = default; if (index < letters.Length - 1) { nextIndexLetter = letters.Get(index + 1); } bool isPreviousLetterNonConnectable = index == 0 || (previousIndexLetter < 0xFFFF && !TextUtils.IsGlyphFixedArabicCharacter((char)previousIndexLetter)) || previousIndexLetter == (int)ArabicGeneralLetters.Hamza || previousIndexLetter == (int)ArabicGeneralLetters.AlefMaddaAbove || previousIndexLetter == (int)ArabicGeneralLetters.AlefHamzaAbove || previousIndexLetter == (int)ArabicGeneralLetters.AlefHamzaBelow || previousIndexLetter == (int)ArabicGeneralLetters.WawHamzaAbove || previousIndexLetter == (int)ArabicGeneralLetters.Alef || previousIndexLetter == (int)ArabicGeneralLetters.Dal || previousIndexLetter == (int)ArabicGeneralLetters.Thal || previousIndexLetter == (int)ArabicGeneralLetters.Reh || previousIndexLetter == (int)ArabicGeneralLetters.Zain || previousIndexLetter == (int)ArabicGeneralLetters.Jeh || previousIndexLetter == (int)ArabicGeneralLetters.Waw || previousIndexLetter == (int)ArabicIsolatedLetters.AlefMaddaAbove || previousIndexLetter == (int)ArabicIsolatedLetters.AlefHamzaAbove || previousIndexLetter == (int)ArabicIsolatedLetters.AlefHamzaBelow || previousIndexLetter == (int)ArabicIsolatedLetters.WawHamzaAbove || previousIndexLetter == (int)ArabicIsolatedLetters.Alef || previousIndexLetter == (int)ArabicIsolatedLetters.Hamza || previousIndexLetter == (int)ArabicIsolatedLetters.Dal || previousIndexLetter == (int)ArabicIsolatedLetters.Thal || previousIndexLetter == (int)ArabicIsolatedLetters.Reh || previousIndexLetter == (int)ArabicIsolatedLetters.Zain || previousIndexLetter == (int)ArabicIsolatedLetters.Jeh || previousIndexLetter == (int)ArabicIsolatedLetters.Waw || previousIndexLetter == (int)SpecialCharacters.ZeroWidthNoJoiner; bool canThisLetterBeLeading = currentIndexLetter != ' ' && currentIndexLetter != (int)ArabicGeneralLetters.Hamza && currentIndexLetter != (int)ArabicGeneralLetters.AlefHamzaAbove && currentIndexLetter != (int)ArabicGeneralLetters.AlefHamzaBelow && currentIndexLetter != (int)ArabicGeneralLetters.AlefMaddaAbove && currentIndexLetter != (int)ArabicGeneralLetters.WawHamzaAbove && currentIndexLetter != (int)ArabicGeneralLetters.Alef && currentIndexLetter != (int)ArabicGeneralLetters.Dal && currentIndexLetter != (int)ArabicGeneralLetters.Thal && currentIndexLetter != (int)ArabicGeneralLetters.Reh && currentIndexLetter != (int)ArabicGeneralLetters.Zain && currentIndexLetter != (int)ArabicGeneralLetters.Jeh && currentIndexLetter != (int)ArabicGeneralLetters.Waw && currentIndexLetter != (int)SpecialCharacters.ZeroWidthNoJoiner; bool isNextLetterConnectable = index < letters.Length - 1 && (nextIndexLetter < 0xFFFF && TextUtils.IsGlyphFixedArabicCharacter((char)nextIndexLetter)) && nextIndexLetter != (int)ArabicGeneralLetters.Hamza && nextIndexLetter != (int)SpecialCharacters.ZeroWidthNoJoiner; return(isPreviousLetterNonConnectable && canThisLetterBeLeading && isNextLetterConnectable); }
/// <summary> /// Is the letter at provided index a middle letter? /// </summary> /// <returns><see langword="true" /> if the letter is a middle letter</returns> private static bool IsMiddleLetter(FastStringBuilder letters, int index) { var currentIndexLetter = letters.Get(index); int previousIndexLetter = default; if (index != 0) { previousIndexLetter = letters.Get(index - 1); } int nextIndexLetter = default; if (index < letters.Length - 1) { nextIndexLetter = letters.Get(index + 1); } bool middleLetterCheck = index != 0 && currentIndexLetter != (int)ArabicGeneralLetters.Hamza && currentIndexLetter != (int)ArabicGeneralLetters.AlefMaddaAbove && currentIndexLetter != (int)ArabicGeneralLetters.AlefHamzaAbove && currentIndexLetter != (int)ArabicGeneralLetters.AlefHamzaBelow && currentIndexLetter != (int)ArabicGeneralLetters.WawHamzaAbove && currentIndexLetter != (int)ArabicGeneralLetters.Alef && currentIndexLetter != (int)ArabicGeneralLetters.Dal && currentIndexLetter != (int)ArabicGeneralLetters.Thal && currentIndexLetter != (int)ArabicGeneralLetters.Reh && currentIndexLetter != (int)ArabicGeneralLetters.Zain && currentIndexLetter != (int)ArabicGeneralLetters.Jeh && currentIndexLetter != (int)ArabicGeneralLetters.Waw && currentIndexLetter != (int)SpecialCharacters.ZeroWidthNoJoiner; bool previousLetterCheck = index != 0 && previousIndexLetter != (int)ArabicIsolatedLetters.Hamza && previousIndexLetter != (int)ArabicIsolatedLetters.AlefMaddaAbove && previousIndexLetter != (int)ArabicIsolatedLetters.AlefHamzaAbove && previousIndexLetter != (int)ArabicIsolatedLetters.AlefHamzaBelow && previousIndexLetter != (int)ArabicIsolatedLetters.WawHamzaAbove && previousIndexLetter != (int)ArabicGeneralLetters.Alef && previousIndexLetter != (int)ArabicGeneralLetters.Dal && previousIndexLetter != (int)ArabicGeneralLetters.Thal && previousIndexLetter != (int)ArabicGeneralLetters.Reh && previousIndexLetter != (int)ArabicGeneralLetters.Zain && previousIndexLetter != (int)ArabicGeneralLetters.Jeh && previousIndexLetter != (int)ArabicGeneralLetters.Waw && previousIndexLetter != (int)ArabicGeneralLetters.AlefMaddaAbove && previousIndexLetter != (int)ArabicGeneralLetters.AlefHamzaAbove && previousIndexLetter != (int)ArabicGeneralLetters.AlefHamzaBelow && previousIndexLetter != (int)ArabicGeneralLetters.WawHamzaAbove && previousIndexLetter != (int)ArabicGeneralLetters.Hamza && previousIndexLetter != (int)ArabicIsolatedLetters.Alef && previousIndexLetter != (int)ArabicIsolatedLetters.Dal && previousIndexLetter != (int)ArabicIsolatedLetters.Thal && previousIndexLetter != (int)ArabicIsolatedLetters.Reh && previousIndexLetter != (int)ArabicIsolatedLetters.Zain && previousIndexLetter != (int)ArabicIsolatedLetters.Jeh && previousIndexLetter != (int)ArabicIsolatedLetters.Waw && previousIndexLetter != (int)SpecialCharacters.ZeroWidthNoJoiner && (previousIndexLetter < 0xFFFF && TextUtils.IsGlyphFixedArabicCharacter((char)previousIndexLetter)); bool nextLetterCheck = index < letters.Length - 1 && (nextIndexLetter < 0xFFFF && TextUtils.IsGlyphFixedArabicCharacter((char)nextIndexLetter)) && nextIndexLetter != (int)SpecialCharacters.ZeroWidthNoJoiner && nextIndexLetter != (int)ArabicGeneralLetters.Hamza && nextIndexLetter != (int)ArabicIsolatedLetters.Hamza; return(nextLetterCheck && previousLetterCheck && middleLetterCheck); }
/// <summary> /// Fixes the flow of the text. /// </summary> public static void Fix(FastStringBuilder input, FastStringBuilder output, bool farsi, bool fixTextTags, bool preserveNumbers) { // Some texts like tags, English words and numbers need to be displayed in their original order. // This list keeps the characters that their order should be reserved and streams reserved texts into final letters. LtrTextHolder.Clear(); for (int i = input.Length - 1; i >= 0; i--) { bool isInMiddle = i > 0 && i < input.Length - 1; bool isAtBeginning = i == 0; bool isAtEnd = i == input.Length - 1; char characterAtThisIndex = input.Get(i); char nextCharacter = default; if (!isAtEnd) { nextCharacter = input.Get(i + 1); } char previousCharacter = default; if (!isAtBeginning) { previousCharacter = input.Get(i - 1); } if (char.IsPunctuation(characterAtThisIndex) || char.IsSymbol(characterAtThisIndex)) { if (fixTextTags) { if (characterAtThisIndex == '>') { // We need to check if it is actually the beginning of a tag. bool isValidTag = false; // If > is at the end of the text (At beginning of the array), it can't be a tag if (isAtEnd == false) { for (int j = i - 1; j >= 0; j--) { // Tags do not have space inside if (input.Get(j) == ' ') { break; } // Tags do not have RTL characters inside if (TextUtils.IsRTLCharacter(input.Get(j))) { break; } if (input.Get(j) == '<') { isValidTag = true; break; } } } if (LtrTextHolder.Count > 0 && isValidTag) { for (int j = 0; j < LtrTextHolder.Count; j++) { output.Append(LtrTextHolder[LtrTextHolder.Count - 1 - j]); } LtrTextHolder.Clear(); } } } if (characterAtThisIndex == ')') { if (isInMiddle) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); if (isAfterRTLCharacter || isBeforeRTLCharacter) { characterAtThisIndex = '('; } } else if (isAtEnd) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); if (isAfterRTLCharacter) { characterAtThisIndex = '('; } } else if (isAtBeginning) { bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); if (isBeforeRTLCharacter) { characterAtThisIndex = '('; } } } else if (characterAtThisIndex == '(') { if (isInMiddle) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); if (isAfterRTLCharacter || isBeforeRTLCharacter) { characterAtThisIndex = ')'; } } else if (isAtEnd) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); if (isAfterRTLCharacter) { characterAtThisIndex = ')'; } } else if (isAtBeginning) { bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); if (isBeforeRTLCharacter) { characterAtThisIndex = ')'; } } } else if (characterAtThisIndex == '«') { if (isInMiddle) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); if (isAfterRTLCharacter || isBeforeRTLCharacter) { characterAtThisIndex = '»'; } } else if (isAtEnd) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); if (isAfterRTLCharacter) { characterAtThisIndex = '»'; } } else if (isAtBeginning) { bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); if (isBeforeRTLCharacter) { characterAtThisIndex = '»'; } } } else if (characterAtThisIndex == '»') { if (isInMiddle) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); if (isAfterRTLCharacter || isBeforeRTLCharacter) { characterAtThisIndex = '«'; } } else if (isAtEnd) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); if (isAfterRTLCharacter) { characterAtThisIndex = '«'; } } else if (isAtBeginning) { bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); if (isBeforeRTLCharacter) { characterAtThisIndex = '«'; } } } if (isInMiddle) { bool isAfterRTLCharacter = TextUtils.IsRTLCharacter(previousCharacter); bool isBeforeRTLCharacter = TextUtils.IsRTLCharacter(nextCharacter); bool isBeforeWhiteSpace = char.IsWhiteSpace(nextCharacter); bool isAfterWhiteSpace = char.IsWhiteSpace(previousCharacter); bool isUnderline = characterAtThisIndex == '_'; bool isSpecialPunctuation = characterAtThisIndex == '.' || characterAtThisIndex == '،' || characterAtThisIndex == '؛'; if (isBeforeRTLCharacter && isAfterRTLCharacter || isAfterWhiteSpace && isSpecialPunctuation || isBeforeWhiteSpace && isAfterRTLCharacter || isBeforeRTLCharacter && isAfterWhiteSpace || (isBeforeRTLCharacter || isAfterRTLCharacter) && isUnderline) { if (LtrTextHolder.Count > 0) { for (int j = 0; j < LtrTextHolder.Count; j++) { output.Append(LtrTextHolder[LtrTextHolder.Count - 1 - j]); } LtrTextHolder.Clear(); } output.Append(characterAtThisIndex); } else { LtrTextHolder.Add(characterAtThisIndex); } } else if (isAtEnd) { LtrTextHolder.Add(characterAtThisIndex); } else if (isAtBeginning) { output.Append(characterAtThisIndex); } if (fixTextTags) { if (characterAtThisIndex == '<') { bool valid = false; if (isAtBeginning == false) { for (int j = i + 1; j < input.Length; j++) { // Tags do not have space inside if (input.Get(j) == ' ') { break; } // Tags do not have RTL characters inside if (TextUtils.IsRTLCharacter(input.Get(j))) { break; } if (input.Get(j) == '>') { valid = true; break; } } } if (LtrTextHolder.Count > 0 && valid) { for (int j = 0; j < LtrTextHolder.Count; j++) { output.Append(LtrTextHolder[LtrTextHolder.Count - 1 - j]); } LtrTextHolder.Clear(); } } } continue; } if (isInMiddle) { bool isAfterEnglishChar = TextUtils.IsEnglishLetter(previousCharacter); bool isBeforeEnglishChar = TextUtils.IsEnglishLetter(nextCharacter); bool isAfterNumber = TextUtils.IsNumber(previousCharacter, preserveNumbers, farsi); bool isBeforeNumber = TextUtils.IsNumber(nextCharacter, preserveNumbers, farsi); bool isAfterSymbol = char.IsSymbol(previousCharacter); bool isBeforeSymbol = char.IsSymbol(nextCharacter); // For cases where english words and farsi/arabic are mixed. This allows for using farsi/arabic, english and numbers in one sentence. // If the space is between numbers,symbols or English words, keep the order if (characterAtThisIndex == ' ' && (isBeforeEnglishChar || isBeforeNumber || isBeforeSymbol) && (isAfterEnglishChar || isAfterNumber || isAfterSymbol)) { LtrTextHolder.Add(characterAtThisIndex); continue; } } if (TextUtils.IsEnglishLetter(characterAtThisIndex) || TextUtils.IsNumber(characterAtThisIndex, preserveNumbers, farsi)) { LtrTextHolder.Add(characterAtThisIndex); continue; } if (characterAtThisIndex >= (char)0xD800 && characterAtThisIndex <= (char)0xDBFF || characterAtThisIndex >= (char)0xDC00 && characterAtThisIndex <= (char)0xDFFF) { LtrTextHolder.Add(characterAtThisIndex); continue; } if (LtrTextHolder.Count > 0) { for (int j = 0; j < LtrTextHolder.Count; j++) { output.Append(LtrTextHolder[LtrTextHolder.Count - 1 - j]); } LtrTextHolder.Clear(); } if (characterAtThisIndex != 0xFFFF && characterAtThisIndex != (int)GeneralLetters.ZeroWidthNoJoiner) { output.Append(characterAtThisIndex); } } if (LtrTextHolder.Count > 0) { for (int j = 0; j < LtrTextHolder.Count; j++) { output.Append(LtrTextHolder[LtrTextHolder.Count - 1 - j]); } LtrTextHolder.Clear(); } }