2024-01-10 18:54:00 +01:00
|
|
|
|
/* Copyright (C) 2018-2024 Andreas Shimokawa, Carsten Pfeiffer, Roi Greenberg
|
2019-02-13 20:43:30 +01:00
|
|
|
|
|
|
|
|
|
This file is part of Gadgetbridge.
|
|
|
|
|
|
|
|
|
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
|
|
|
|
it under the terms of the GNU Affero General Public License as published
|
|
|
|
|
by the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
|
|
Gadgetbridge is distributed in the hope that it will be useful,
|
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
2024-01-10 18:54:00 +01:00
|
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
2018-08-25 21:29:46 +02:00
|
|
|
|
package nodomain.freeyourgadget.gadgetbridge.util;
|
|
|
|
|
|
2018-09-04 01:00:14 +02:00
|
|
|
|
import android.text.TextUtils;
|
2018-08-25 21:29:46 +02:00
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
2018-09-04 01:00:14 +02:00
|
|
|
|
import java.util.List;
|
2018-08-25 21:29:46 +02:00
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
|
|
|
|
2018-09-04 01:00:14 +02:00
|
|
|
|
public class RtlUtils {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks the status of right-to-left option
|
|
|
|
|
* @return true if right-to-left option is On, and false, if Off or not exist
|
|
|
|
|
*/
|
|
|
|
|
public static boolean rtlSupport()
|
|
|
|
|
{
|
2018-09-05 21:32:06 +02:00
|
|
|
|
return GBApplication.getPrefs().getBoolean(GBPrefs.RTL_SUPPORT, false);
|
2018-09-04 01:00:14 +02:00
|
|
|
|
}
|
2018-09-02 04:04:32 +02:00
|
|
|
|
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public enum characterType{
|
2018-09-02 04:04:32 +02:00
|
|
|
|
ltr,
|
|
|
|
|
rtl,
|
|
|
|
|
rtl_arabic,
|
|
|
|
|
punctuation,
|
|
|
|
|
lineEnd,
|
|
|
|
|
space,
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-04 00:10:54 +02:00
|
|
|
|
public static characterType getCharacterType(Character c){
|
2018-09-02 04:04:32 +02:00
|
|
|
|
characterType type;
|
|
|
|
|
switch (Character.getDirectionality(c)) {
|
|
|
|
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT:
|
|
|
|
|
type = characterType.rtl;
|
|
|
|
|
break;
|
|
|
|
|
case Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC:
|
|
|
|
|
type = characterType.rtl_arabic;
|
|
|
|
|
break;
|
|
|
|
|
case Character.DIRECTIONALITY_EUROPEAN_NUMBER_SEPARATOR:
|
|
|
|
|
case Character.DIRECTIONALITY_EUROPEAN_NUMBER_TERMINATOR:
|
|
|
|
|
case Character.DIRECTIONALITY_COMMON_NUMBER_SEPARATOR:
|
|
|
|
|
case Character.DIRECTIONALITY_OTHER_NEUTRALS:
|
|
|
|
|
type = characterType.punctuation;
|
|
|
|
|
break;
|
|
|
|
|
case Character.DIRECTIONALITY_BOUNDARY_NEUTRAL:
|
|
|
|
|
case Character.DIRECTIONALITY_PARAGRAPH_SEPARATOR:
|
|
|
|
|
type = characterType.lineEnd;
|
|
|
|
|
break;
|
|
|
|
|
case Character.DIRECTIONALITY_WHITESPACE:
|
|
|
|
|
type = characterType.space;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
type = characterType.ltr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return type;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-25 21:29:46 +02:00
|
|
|
|
/**
|
|
|
|
|
* Checks the status of right-to-left option
|
|
|
|
|
* @return true if right-to-left option is On, and false, if Off or not exist
|
|
|
|
|
*/
|
|
|
|
|
public static boolean contextualSupport()
|
|
|
|
|
{
|
2018-09-05 21:32:06 +02:00
|
|
|
|
return GBApplication.getPrefs().getBoolean(GBPrefs.RTL_CONTEXTUAL_ARABIC, false);
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//map with brackets chars to change there direction
|
|
|
|
|
private static Map<Character, Character> directionSignsMap = new HashMap<Character, Character>(){
|
|
|
|
|
{
|
|
|
|
|
put('(', ')'); put(')', '('); put('[', ']'); put(']', '['); put('{','}'); put('}','{');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return true if the char is in the rtl range, otherwise false
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean isHebrew(Character c){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-02 04:04:32 +02:00
|
|
|
|
return getCharacterType(c) == characterType.rtl;
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return true if the char is in the rtl range, otherwise false
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean isArabic(Character c){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-02 04:04:32 +02:00
|
|
|
|
return getCharacterType(c) == characterType.rtl_arabic;
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return true if the char is in the rtl range, otherwise false
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean isLtr(Character c){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-02 04:04:32 +02:00
|
|
|
|
return getCharacterType(c) == characterType.ltr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return true if the char is in the rtl range, otherwise false
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean isRtl(Character c){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-02 04:04:32 +02:00
|
|
|
|
return (getCharacterType(c) == characterType.rtl) || (getCharacterType(c) == characterType.rtl_arabic);
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return true if the char is in the punctuations range, otherwise false
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean isPunctuations(Character c){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-02 04:04:32 +02:00
|
|
|
|
return getCharacterType(c) == characterType.punctuation;
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @return true if the char is in the end of word list, otherwise false
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean isSpaceSign(Character c){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-02 04:04:32 +02:00
|
|
|
|
return getCharacterType(c) == characterType.space;
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-26 01:06:45 +02:00
|
|
|
|
/**
|
|
|
|
|
* @return true if the char is in the end of word list, otherwise false
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean isEndLineSign(Character c){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-02 04:04:32 +02:00
|
|
|
|
return getCharacterType(c) == characterType.lineEnd;
|
2018-08-26 01:06:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
2018-08-25 21:29:46 +02:00
|
|
|
|
//map from Arabian characters to their contextual form in the beginning of the word
|
|
|
|
|
private static Map<Character, Character> contextualArabicIsolated = new HashMap<Character, Character>(){
|
|
|
|
|
{
|
|
|
|
|
put('ا', '\uFE8D');
|
|
|
|
|
put('ب', '\uFE8F');
|
|
|
|
|
put('ت', '\uFE95');
|
|
|
|
|
put('ث', '\uFE99');
|
|
|
|
|
put('ج', '\uFE9D');
|
|
|
|
|
put('ح', '\uFEA1');
|
|
|
|
|
put('خ', '\uFEA5');
|
|
|
|
|
put('د', '\uFEA9');
|
|
|
|
|
put('ذ', '\uFEAB');
|
|
|
|
|
put('ر', '\uFEAD');
|
|
|
|
|
put('ز', '\uFEAF');
|
|
|
|
|
put('س', '\uFEB1');
|
|
|
|
|
put('ش', '\uFEB5');
|
|
|
|
|
put('ص', '\uFEB9');
|
|
|
|
|
put('ض', '\uFEBD');
|
|
|
|
|
put('ط', '\uFEC1');
|
|
|
|
|
put('ظ', '\uFEC5');
|
|
|
|
|
put('ع', '\uFEC9');
|
|
|
|
|
put('غ', '\uFECD');
|
|
|
|
|
put('ف', '\uFED1');
|
|
|
|
|
put('ق', '\uFED5');
|
|
|
|
|
put('ك', '\uFED9');
|
|
|
|
|
put('ل', '\uFEDD');
|
|
|
|
|
put('م', '\uFEE1');
|
|
|
|
|
put('ن', '\uFEE5');
|
|
|
|
|
put('ه', '\uFEE9');
|
|
|
|
|
put('و', '\uFEED');
|
|
|
|
|
put('ي', '\uFEF1');
|
|
|
|
|
put('آ', '\uFE81');
|
|
|
|
|
put('ة', '\uFE93');
|
|
|
|
|
put('ى', '\uFEEF');
|
|
|
|
|
put('ئ', '\uFE89');
|
|
|
|
|
put('إ', '\uFE87');
|
|
|
|
|
put('أ', '\uFE83');
|
|
|
|
|
put('ء', '\uFE80');
|
|
|
|
|
put('ؤ', '\uFE85');
|
|
|
|
|
put((char)('ل' + 'آ'), '\uFEF5');
|
|
|
|
|
put((char)('ل' + 'أ'), '\uFEF7');
|
|
|
|
|
put((char)('ل' + 'إ'), '\uFEF9');
|
|
|
|
|
put((char)('ل' + 'ا'), '\uFEFB');
|
2018-09-05 21:39:38 +02:00
|
|
|
|
//Farsi
|
|
|
|
|
put('گ', '\uFB92');
|
|
|
|
|
put('ک', '\uFB8E');
|
|
|
|
|
put('چ', '\uFB7A');
|
|
|
|
|
put('پ', '\uFB56');
|
|
|
|
|
put('ژ', '\uFB8A');
|
|
|
|
|
put('ی', '\uFBFC');
|
2018-08-25 21:29:46 +02:00
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//map from Arabian characters to their contextual form in the beginning of the word
|
|
|
|
|
private static Map<Character, Character> contextualArabicBeginning = new HashMap<Character, Character>(){
|
|
|
|
|
{
|
|
|
|
|
put('ب', '\uFE91');
|
|
|
|
|
put('ت', '\uFE97');
|
|
|
|
|
put('ث', '\uFE9B');
|
|
|
|
|
put('ج', '\uFE9F');
|
|
|
|
|
put('ح', '\uFEA3');
|
|
|
|
|
put('خ', '\uFEA7');
|
|
|
|
|
put('س', '\uFEB3');
|
|
|
|
|
put('ش', '\uFEB7');
|
|
|
|
|
put('ص', '\uFEBB');
|
|
|
|
|
put('ض', '\uFEBF');
|
|
|
|
|
put('ط', '\uFEC3');
|
|
|
|
|
put('ظ', '\uFEC7');
|
|
|
|
|
put('ع', '\uFECB');
|
|
|
|
|
put('غ', '\uFECF');
|
|
|
|
|
put('ف', '\uFED3');
|
|
|
|
|
put('ق', '\uFED7');
|
|
|
|
|
put('ك', '\uFEDB');
|
|
|
|
|
put('ل', '\uFEDF');
|
|
|
|
|
put('م', '\uFEE3');
|
|
|
|
|
put('ن', '\uFEE7');
|
|
|
|
|
put('ه', '\uFEEB');
|
|
|
|
|
put('ي', '\uFEF3');
|
|
|
|
|
put('ئ', '\uFE8B');
|
2018-09-05 21:39:38 +02:00
|
|
|
|
//Farsi
|
|
|
|
|
put('گ', '\uFB94');
|
|
|
|
|
put('ک', '\uFB90');
|
|
|
|
|
put('چ', '\uFB7C');
|
|
|
|
|
put('پ', '\uFB58');
|
|
|
|
|
put('ی', '\uFBFE');
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//map from Arabian characters to their contextual form in the middle of the word
|
|
|
|
|
private static Map<Character, Character> contextualArabicMiddle = new HashMap<Character, Character>(){
|
|
|
|
|
{
|
|
|
|
|
put('ب', '\uFE92');
|
|
|
|
|
put('ت', '\uFE98');
|
|
|
|
|
put('ث', '\uFE9C');
|
|
|
|
|
put('ج', '\uFEA0');
|
|
|
|
|
put('ح', '\uFEA4');
|
|
|
|
|
put('خ', '\uFEA8');
|
|
|
|
|
put('س', '\uFEB4');
|
|
|
|
|
put('ش', '\uFEB8');
|
|
|
|
|
put('ص', '\uFEBC');
|
|
|
|
|
put('ض', '\uFEC0');
|
|
|
|
|
put('ط', '\uFEC4');
|
|
|
|
|
put('ظ', '\uFEC8');
|
|
|
|
|
put('ع', '\uFECC');
|
|
|
|
|
put('غ', '\uFED0');
|
|
|
|
|
put('ف', '\uFED4');
|
|
|
|
|
put('ق', '\uFED8');
|
|
|
|
|
put('ك', '\uFEDC');
|
|
|
|
|
put('ل', '\uFEE0');
|
|
|
|
|
put('م', '\uFEE4');
|
|
|
|
|
put('ن', '\uFEE8');
|
|
|
|
|
put('ه', '\uFEEC');
|
|
|
|
|
put('ي', '\uFEF4');
|
|
|
|
|
put('ئ', '\uFE8C');
|
2018-09-05 21:39:38 +02:00
|
|
|
|
//Farsi
|
|
|
|
|
put('گ', '\uFB95');
|
|
|
|
|
put('ک', '\uFB91');
|
|
|
|
|
put('چ', '\uFB7D');
|
|
|
|
|
put('پ', '\uFB59');
|
|
|
|
|
put('ی', '\uFBFF');
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//map from Arabian characters to their contextual form in the end of the word
|
|
|
|
|
private static Map<Character, Character> contextualArabicEnd = new HashMap<Character, Character>(){
|
|
|
|
|
{
|
|
|
|
|
put('ا', '\uFE8E');
|
|
|
|
|
put('ب', '\uFE90');
|
|
|
|
|
put('ت', '\uFE96');
|
|
|
|
|
put('ث', '\uFE9A');
|
|
|
|
|
put('ج', '\uFE9E');
|
|
|
|
|
put('ح', '\uFEA2');
|
|
|
|
|
put('خ', '\uFEA6');
|
|
|
|
|
put('د', '\uFEAA');
|
|
|
|
|
put('ذ', '\uFEAC');
|
|
|
|
|
put('ر', '\uFEAE');
|
|
|
|
|
put('ز', '\uFEB0');
|
|
|
|
|
put('س', '\uFEB2');
|
|
|
|
|
put('ش', '\uFEB6');
|
|
|
|
|
put('ص', '\uFEBA');
|
|
|
|
|
put('ض', '\uFEBE');
|
|
|
|
|
put('ط', '\uFEC2');
|
|
|
|
|
put('ظ', '\uFEC6');
|
|
|
|
|
put('ع', '\uFECA');
|
|
|
|
|
put('غ', '\uFECE');
|
|
|
|
|
put('ف', '\uFED2');
|
|
|
|
|
put('ق', '\uFED6');
|
|
|
|
|
put('ك', '\uFEDA');
|
|
|
|
|
put('ل', '\uFEDE');
|
|
|
|
|
put('م', '\uFEE2');
|
|
|
|
|
put('ن', '\uFEE6');
|
|
|
|
|
put('ه', '\uFEEA');
|
|
|
|
|
put('و', '\uFEEE');
|
|
|
|
|
put('ي', '\uFEF2');
|
|
|
|
|
put('آ', '\uFE82');
|
|
|
|
|
put('ة', '\uFE94');
|
|
|
|
|
put('ى', '\uFEF0');
|
|
|
|
|
put('ئ', '\uFE8A');
|
|
|
|
|
put('إ', '\uFE88');
|
|
|
|
|
put('أ', '\uFE84');
|
|
|
|
|
put('ؤ', '\uFE86');
|
|
|
|
|
put((char)('ل' + 'آ'), '\uFEF6');
|
|
|
|
|
put((char)('ل' + 'أ'), '\uFEF8');
|
|
|
|
|
put((char)('ل' + 'إ'), '\uFEFA');
|
|
|
|
|
put((char)('ل' + 'ا'), '\uFEFC');
|
2018-09-05 21:39:38 +02:00
|
|
|
|
//Farsi
|
|
|
|
|
put('گ', '\uFB93');
|
|
|
|
|
put('ک', '\uFB8F');
|
|
|
|
|
put('چ', '\uFB7B');
|
|
|
|
|
put('پ', '\uFB57');
|
|
|
|
|
put('ژ', '\uFB8B');
|
|
|
|
|
put('ی', '\uFBFD');
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|
|
|
|
|
};
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public enum contextualState{
|
2018-08-25 21:29:46 +02:00
|
|
|
|
isolate,
|
|
|
|
|
begin,
|
|
|
|
|
middle,
|
|
|
|
|
end
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static boolean exceptionAfterLam(Character c){
|
2018-08-25 21:29:46 +02:00
|
|
|
|
switch (c){
|
|
|
|
|
case '\u0622':
|
|
|
|
|
case '\u0623':
|
|
|
|
|
case '\u0625':
|
|
|
|
|
case '\u0627':
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-26 01:09:50 +02:00
|
|
|
|
/**
|
|
|
|
|
* This function return the contextual form of Arabic characters in a given state
|
|
|
|
|
* @param c - the character to convert
|
|
|
|
|
* @param state - the character state: beginning, middle, end or isolated
|
|
|
|
|
* @return the contextual character
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static Character getContextualSymbol(Character c, contextualState state) {
|
2018-08-25 21:29:46 +02:00
|
|
|
|
Character newChar;
|
|
|
|
|
switch (state){
|
|
|
|
|
case begin:
|
|
|
|
|
newChar = contextualArabicBeginning.get(c);
|
|
|
|
|
break;
|
|
|
|
|
case middle:
|
|
|
|
|
newChar = contextualArabicMiddle.get(c);
|
|
|
|
|
break;
|
|
|
|
|
case end:
|
|
|
|
|
newChar = contextualArabicEnd.get(c);
|
|
|
|
|
break;
|
|
|
|
|
case isolate:
|
|
|
|
|
default:
|
|
|
|
|
newChar = contextualArabicIsolated.get(c);;
|
|
|
|
|
}
|
|
|
|
|
if (newChar != null){
|
|
|
|
|
return newChar;
|
|
|
|
|
} else{
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-26 01:09:50 +02:00
|
|
|
|
/**
|
|
|
|
|
* This function return the contextual state of a given character, depend of the previous
|
|
|
|
|
* character state and the next charachter.
|
|
|
|
|
* @param prevState - previous character state or isolated if none
|
|
|
|
|
* @param curChar - the current character
|
|
|
|
|
* @param nextChar - the next character or null if none
|
|
|
|
|
* @return the current character contextual state
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static contextualState getCharContextualState(contextualState prevState, Character curChar, Character nextChar) {
|
2018-08-26 01:09:50 +02:00
|
|
|
|
contextualState curState;
|
|
|
|
|
if ((prevState == contextualState.isolate || prevState == contextualState.end) &&
|
|
|
|
|
contextualArabicBeginning.containsKey(curChar) &&
|
|
|
|
|
contextualArabicEnd.containsKey(nextChar)){
|
|
|
|
|
|
|
|
|
|
curState = contextualState.begin;
|
|
|
|
|
|
|
|
|
|
} else if ((prevState == contextualState.begin || prevState == contextualState.middle) &&
|
|
|
|
|
contextualArabicEnd.containsKey(curChar)){
|
|
|
|
|
|
|
|
|
|
if (contextualArabicMiddle.containsKey(curChar) && contextualArabicEnd.containsKey(nextChar)){
|
|
|
|
|
curState = contextualState.middle;
|
|
|
|
|
}else{
|
|
|
|
|
curState = contextualState.end;
|
|
|
|
|
}
|
|
|
|
|
}else{
|
|
|
|
|
curState = contextualState.isolate;
|
|
|
|
|
}
|
|
|
|
|
return curState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* this function convert given string to it's contextual form
|
|
|
|
|
* @param s - the given string
|
|
|
|
|
* @return the contextual form of the string
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static String convertToContextual(String s){
|
2018-08-25 21:29:46 +02:00
|
|
|
|
if (s == null || s.isEmpty() || s.length() == 1){
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int length = s.length();
|
|
|
|
|
StringBuilder newWord = new StringBuilder(length);
|
|
|
|
|
|
|
|
|
|
Character curChar, nextChar = s.charAt(0);
|
|
|
|
|
contextualState prevState = contextualState.isolate;
|
|
|
|
|
contextualState curState = contextualState.isolate;
|
2018-08-26 01:06:45 +02:00
|
|
|
|
|
2018-08-25 21:29:46 +02:00
|
|
|
|
for (int i = 0; i < length - 1; i++){
|
|
|
|
|
curChar = nextChar;
|
|
|
|
|
nextChar = s.charAt(i + 1);
|
|
|
|
|
|
|
|
|
|
if (curChar == 'ل' && exceptionAfterLam(nextChar)){
|
|
|
|
|
i++;
|
|
|
|
|
curChar = (char)(nextChar + curChar);
|
|
|
|
|
if (i < length - 1) {
|
|
|
|
|
nextChar = s.charAt(i + 1);
|
|
|
|
|
}else{
|
|
|
|
|
nextChar = curChar;
|
|
|
|
|
prevState = curState;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
curState = getCharContextualState(prevState, curChar, nextChar);
|
|
|
|
|
newWord.append(getContextualSymbol(curChar, curState));
|
|
|
|
|
prevState = curState;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
curState = getCharContextualState(prevState, nextChar, null);
|
|
|
|
|
newWord.append(getContextualSymbol(nextChar, curState));
|
|
|
|
|
|
|
|
|
|
return newWord.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The function get a string and reverse it.
|
|
|
|
|
* in case of end-of-word sign, it will leave it at the end.
|
|
|
|
|
* in case of sign with direction like brackets, it will change the direction.
|
|
|
|
|
* @param s - the string to reverse
|
|
|
|
|
* @return reversed string
|
|
|
|
|
*/
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static String reverse(String s) {
|
2018-08-25 21:29:46 +02:00
|
|
|
|
int j = s.length();
|
2018-08-26 01:06:45 +02:00
|
|
|
|
int isEndLine = 0;
|
2018-08-25 21:29:46 +02:00
|
|
|
|
char[] newWord = new char[j];
|
|
|
|
|
|
|
|
|
|
if (j == 0) {
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-26 01:06:45 +02:00
|
|
|
|
for (int i = 0; i < s.length() - isEndLine; i++) {
|
2018-08-25 21:29:46 +02:00
|
|
|
|
if (directionSignsMap.containsKey(s.charAt(i))) {
|
|
|
|
|
newWord[--j] = directionSignsMap.get(s.charAt(i));
|
|
|
|
|
} else {
|
|
|
|
|
newWord[--j] = s.charAt(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-08-26 01:06:45 +02:00
|
|
|
|
|
2018-08-25 21:29:46 +02:00
|
|
|
|
return new String(newWord);
|
|
|
|
|
}
|
2018-09-04 00:10:54 +02:00
|
|
|
|
|
2018-09-05 00:39:01 +02:00
|
|
|
|
public static String fixWhitespace(String s){
|
2018-09-04 00:10:54 +02:00
|
|
|
|
int length = s.length();
|
|
|
|
|
|
|
|
|
|
if (length > 0 && isSpaceSign(s.charAt(length - 1))){
|
|
|
|
|
return s.charAt(length - 1) + s.substring(0, length - 1);
|
|
|
|
|
} else {
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-09-04 01:00:14 +02:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The function get a string and fix the rtl words.
|
|
|
|
|
* since simple reverse puts the beginning of the text at the end, the text should have been from bottom to top.
|
|
|
|
|
* To avoid that, we save the text in lines (line max size can be change in the settings)
|
2018-11-03 23:26:07 +01:00
|
|
|
|
* @param oldString - the string to fix.
|
2018-09-04 01:00:14 +02:00
|
|
|
|
* @return a fix string.
|
|
|
|
|
*/
|
2018-11-03 23:26:07 +01:00
|
|
|
|
public static String fixRtl(String oldString) {
|
|
|
|
|
if (oldString == null || oldString.isEmpty()){
|
|
|
|
|
return oldString;
|
2018-09-04 01:00:14 +02:00
|
|
|
|
}
|
2018-11-03 23:26:07 +01:00
|
|
|
|
debug("before: |" + org.apache.commons.lang3.StringEscapeUtils.escapeJava(oldString) + "|");
|
2018-09-04 01:00:14 +02:00
|
|
|
|
|
2018-11-03 23:26:07 +01:00
|
|
|
|
int length = oldString.length();
|
2018-09-04 01:00:14 +02:00
|
|
|
|
String newString = "";
|
|
|
|
|
List<String> lines = new ArrayList<>();
|
|
|
|
|
char[] newWord = new char[length];
|
2018-09-05 00:39:01 +02:00
|
|
|
|
int line_max_size = GBApplication.getPrefs().getInt("rtl_max_line_length", 18);
|
2018-09-04 01:00:14 +02:00
|
|
|
|
|
|
|
|
|
int startPos = 0;
|
|
|
|
|
int endPos = 0;
|
|
|
|
|
characterType CurRtlType = isRtl(oldString.charAt(0))? characterType.rtl : characterType.ltr;
|
|
|
|
|
characterType PhraseRtlType = CurRtlType;
|
|
|
|
|
|
|
|
|
|
Character c;
|
|
|
|
|
// String word = "", phrase = "", line = "";
|
|
|
|
|
StringBuilder word = new StringBuilder();
|
|
|
|
|
StringBuilder phrase = new StringBuilder();
|
|
|
|
|
StringBuilder line = new StringBuilder();
|
|
|
|
|
String phraseString = "";
|
|
|
|
|
boolean addCharToWord = false;
|
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
|
|
|
c = oldString.charAt(i);
|
|
|
|
|
addCharToWord = false;
|
2018-11-03 23:26:07 +01:00
|
|
|
|
debug("char: " + c + " :" + Character.getDirectionality(c));
|
|
|
|
|
// debug( "hex : " + (int)c);
|
2018-09-04 01:00:14 +02:00
|
|
|
|
|
|
|
|
|
if (isLtr(c)){
|
|
|
|
|
CurRtlType = characterType.ltr;
|
|
|
|
|
} else if (isRtl(c)) {
|
|
|
|
|
CurRtlType = characterType.rtl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ((CurRtlType == PhraseRtlType) && !(isSpaceSign(c) || isEndLineSign(c))){
|
2018-11-03 23:26:07 +01:00
|
|
|
|
debug("add: " + c + " to: " + word);
|
2018-09-04 01:00:14 +02:00
|
|
|
|
word.append(c);
|
|
|
|
|
addCharToWord = true;
|
|
|
|
|
if (i < length - 1) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
do {
|
2018-09-05 22:19:35 +02:00
|
|
|
|
if ((line.length() + phrase.length() + word.length() < line_max_size) ||
|
2018-11-22 18:58:41 +01:00
|
|
|
|
(line.length() == 0 && word.length() >= line_max_size)) {
|
2018-09-04 01:00:14 +02:00
|
|
|
|
if (isSpaceSign(c)) {
|
|
|
|
|
word.append(c);
|
|
|
|
|
addCharToWord = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
phrase.append(word);
|
|
|
|
|
word.setLength(0);
|
|
|
|
|
|
|
|
|
|
if (isSpaceSign(c)) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
phraseString = phrase.toString();
|
2018-11-03 23:26:07 +01:00
|
|
|
|
debug("phrase: |" + phraseString + "|");
|
2018-09-04 01:00:14 +02:00
|
|
|
|
if (PhraseRtlType == characterType.rtl) {
|
|
|
|
|
if (contextualSupport()) {
|
2018-09-05 00:39:01 +02:00
|
|
|
|
phraseString = convertToContextual(phraseString);
|
2018-09-04 01:00:14 +02:00
|
|
|
|
}
|
|
|
|
|
phraseString = reverse(phraseString);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
line.insert(0, fixWhitespace(phraseString));
|
2018-11-03 23:26:07 +01:00
|
|
|
|
debug("line now: |" + line + "|");
|
2018-09-04 01:00:14 +02:00
|
|
|
|
phrase.setLength(0);
|
|
|
|
|
|
|
|
|
|
if (word.length() > 0){
|
|
|
|
|
line.append('\n');
|
|
|
|
|
} else if (isEndLineSign(c)) {
|
|
|
|
|
line.append(c);
|
|
|
|
|
} else if (!addCharToWord) {
|
|
|
|
|
word.append(c);
|
|
|
|
|
if (i == length - 1){
|
|
|
|
|
addCharToWord = true;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
PhraseRtlType = PhraseRtlType == characterType.rtl ? characterType.ltr : characterType.rtl;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines.add(line.toString());
|
2018-11-03 23:26:07 +01:00
|
|
|
|
debug("line: |" + line + "|");
|
2018-09-04 01:00:14 +02:00
|
|
|
|
line.setLength(0);
|
|
|
|
|
|
|
|
|
|
if (word.length() == 0){
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} while (true);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lines.add(line.toString());
|
|
|
|
|
|
|
|
|
|
newString = TextUtils.join("", lines);
|
|
|
|
|
|
2018-11-03 23:26:07 +01:00
|
|
|
|
debug("after : |" + org.apache.commons.lang3.StringEscapeUtils.escapeJava(newString) + "|");
|
2018-09-04 01:00:14 +02:00
|
|
|
|
|
|
|
|
|
return newString;
|
|
|
|
|
}
|
2018-11-03 23:26:07 +01:00
|
|
|
|
|
|
|
|
|
private static void debug(String s) {
|
|
|
|
|
// Log.d("ROIGR", s);
|
|
|
|
|
}
|
2018-08-25 21:29:46 +02:00
|
|
|
|
}
|