rtl.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. // Copyright (c) 2011 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "base/i18n/rtl.h"
  5. #include <stddef.h>
  6. #include <stdint.h>
  7. #include <algorithm>
  8. #include "base/check_op.h"
  9. #include "base/command_line.h"
  10. #include "base/files/file_path.h"
  11. #include "base/i18n/base_i18n_switches.h"
  12. #include "base/logging.h"
  13. #include "base/strings/string_split.h"
  14. #include "base/strings/string_util.h"
  15. #include "base/strings/sys_string_conversions.h"
  16. #include "base/strings/utf_string_conversions.h"
  17. #include "build/build_config.h"
  18. #include "third_party/icu/source/common/unicode/locid.h"
  19. #include "third_party/icu/source/common/unicode/uchar.h"
  20. #include "third_party/icu/source/common/unicode/uscript.h"
  21. #include "third_party/icu/source/i18n/unicode/coll.h"
  22. #if BUILDFLAG(IS_IOS)
  23. #include "base/debug/crash_logging.h"
  24. #include "base/ios/ios_util.h"
  25. #endif
  26. namespace {
  27. // Extract language, country and variant, but ignore keywords. For example,
  28. // en-US, ca@valencia, ca-ES@valencia.
  29. std::string GetLocaleString(const icu::Locale& locale) {
  30. const char* language = locale.getLanguage();
  31. const char* country = locale.getCountry();
  32. const char* variant = locale.getVariant();
  33. std::string result =
  34. (language != nullptr && *language != '\0') ? language : "und";
  35. if (country != nullptr && *country != '\0') {
  36. result += '-';
  37. result += country;
  38. }
  39. if (variant != nullptr && *variant != '\0')
  40. result += '@' + base::ToLowerASCII(variant);
  41. return result;
  42. }
  43. // Returns LEFT_TO_RIGHT or RIGHT_TO_LEFT if |character| has strong
  44. // directionality, returns UNKNOWN_DIRECTION if it doesn't. Please refer to
  45. // http://unicode.org/reports/tr9/ for more information.
  46. base::i18n::TextDirection GetCharacterDirection(UChar32 character) {
  47. static bool has_switch = base::CommandLine::ForCurrentProcess()->HasSwitch(
  48. switches::kForceTextDirection);
  49. if (has_switch) {
  50. base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  51. std::string force_flag =
  52. command_line->GetSwitchValueASCII(switches::kForceTextDirection);
  53. if (force_flag == switches::kForceDirectionRTL)
  54. return base::i18n::RIGHT_TO_LEFT;
  55. if (force_flag == switches::kForceDirectionLTR)
  56. return base::i18n::LEFT_TO_RIGHT;
  57. }
  58. // Now that we have the character, we use ICU in order to query for the
  59. // appropriate Unicode BiDi character type.
  60. int32_t property = u_getIntPropertyValue(character, UCHAR_BIDI_CLASS);
  61. switch (property) {
  62. case U_RIGHT_TO_LEFT:
  63. case U_RIGHT_TO_LEFT_ARABIC:
  64. case U_RIGHT_TO_LEFT_EMBEDDING:
  65. case U_RIGHT_TO_LEFT_OVERRIDE:
  66. return base::i18n::RIGHT_TO_LEFT;
  67. case U_LEFT_TO_RIGHT:
  68. case U_LEFT_TO_RIGHT_EMBEDDING:
  69. case U_LEFT_TO_RIGHT_OVERRIDE:
  70. return base::i18n::LEFT_TO_RIGHT;
  71. }
  72. return base::i18n::UNKNOWN_DIRECTION;
  73. }
  74. } // namespace
  75. namespace base {
  76. namespace i18n {
  77. // Represents the locale-specific ICU text direction.
  78. static TextDirection g_icu_text_direction = UNKNOWN_DIRECTION;
  79. // Convert the ICU default locale to a string.
  80. std::string GetConfiguredLocale() {
  81. return GetLocaleString(icu::Locale::getDefault());
  82. }
  83. // Convert the ICU canonicalized locale to a string.
  84. std::string GetCanonicalLocale(const std::string& locale) {
  85. return GetLocaleString(icu::Locale::createCanonical(locale.c_str()));
  86. }
  87. // Convert Chrome locale name to ICU locale name
  88. std::string ICULocaleName(const std::string& locale_string) {
  89. // If not Spanish, just return it.
  90. if (locale_string.substr(0, 2) != "es")
  91. return locale_string;
  92. // Expand es to es-ES.
  93. if (EqualsCaseInsensitiveASCII(locale_string, "es"))
  94. return "es-ES";
  95. // Map es-419 (Latin American Spanish) to es-FOO depending on the system
  96. // locale. If it's es-RR other than es-ES, map to es-RR. Otherwise, map
  97. // to es-MX (the most populous in Spanish-speaking Latin America).
  98. if (EqualsCaseInsensitiveASCII(locale_string, "es-419")) {
  99. const icu::Locale& locale = icu::Locale::getDefault();
  100. std::string language = locale.getLanguage();
  101. const char* country = locale.getCountry();
  102. if (EqualsCaseInsensitiveASCII(language, "es") &&
  103. !EqualsCaseInsensitiveASCII(country, "es")) {
  104. language += '-';
  105. language += country;
  106. return language;
  107. }
  108. return "es-MX";
  109. }
  110. // Currently, Chrome has only "es" and "es-419", but later we may have
  111. // more specific "es-RR".
  112. return locale_string;
  113. }
  114. void SetICUDefaultLocale(const std::string& locale_string) {
  115. #if BUILDFLAG(IS_IOS)
  116. static base::debug::CrashKeyString* crash_key_locale =
  117. base::debug::AllocateCrashKeyString("icu_locale_input",
  118. base::debug::CrashKeySize::Size256);
  119. base::debug::SetCrashKeyString(crash_key_locale, locale_string);
  120. #endif
  121. icu::Locale locale(ICULocaleName(locale_string).c_str());
  122. UErrorCode error_code = U_ZERO_ERROR;
  123. const char* lang = locale.getLanguage();
  124. if (lang != nullptr && *lang != '\0') {
  125. icu::Locale::setDefault(locale, error_code);
  126. } else {
  127. LOG(ERROR) << "Failed to set the ICU default locale to " << locale_string
  128. << ". Falling back to en-US.";
  129. icu::Locale::setDefault(icu::Locale::getUS(), error_code);
  130. }
  131. g_icu_text_direction = UNKNOWN_DIRECTION;
  132. }
  133. bool IsRTL() {
  134. return ICUIsRTL();
  135. }
  136. void SetRTLForTesting(bool rtl) {
  137. SetICUDefaultLocale(rtl ? "he" : "en");
  138. DCHECK_EQ(rtl, IsRTL());
  139. }
  140. bool ICUIsRTL() {
  141. if (g_icu_text_direction == UNKNOWN_DIRECTION) {
  142. const icu::Locale& locale = icu::Locale::getDefault();
  143. g_icu_text_direction = GetTextDirectionForLocaleInStartUp(locale.getName());
  144. }
  145. return g_icu_text_direction == RIGHT_TO_LEFT;
  146. }
  147. TextDirection GetForcedTextDirection() {
  148. // On iOS, check for RTL forcing.
  149. #if BUILDFLAG(IS_IOS)
  150. if (base::ios::IsInForcedRTL())
  151. return base::i18n::RIGHT_TO_LEFT;
  152. #endif
  153. base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  154. if (command_line->HasSwitch(switches::kForceUIDirection)) {
  155. std::string force_flag =
  156. command_line->GetSwitchValueASCII(switches::kForceUIDirection);
  157. if (force_flag == switches::kForceDirectionLTR)
  158. return base::i18n::LEFT_TO_RIGHT;
  159. if (force_flag == switches::kForceDirectionRTL)
  160. return base::i18n::RIGHT_TO_LEFT;
  161. }
  162. return base::i18n::UNKNOWN_DIRECTION;
  163. }
  164. TextDirection GetTextDirectionForLocaleInStartUp(const char* locale_name) {
  165. // Check for direction forcing.
  166. TextDirection forced_direction = GetForcedTextDirection();
  167. if (forced_direction != UNKNOWN_DIRECTION)
  168. return forced_direction;
  169. // This list needs to be updated in alphabetical order if we add more RTL
  170. // locales.
  171. static const char kRTLLanguageCodes[][3] = {"ar", "fa", "he", "iw", "ur"};
  172. std::vector<StringPiece> locale_split =
  173. SplitStringPiece(locale_name, "-_", KEEP_WHITESPACE, SPLIT_WANT_ALL);
  174. const StringPiece& language_code = locale_split[0];
  175. if (std::binary_search(kRTLLanguageCodes,
  176. kRTLLanguageCodes + std::size(kRTLLanguageCodes),
  177. language_code))
  178. return RIGHT_TO_LEFT;
  179. return LEFT_TO_RIGHT;
  180. }
  181. TextDirection GetTextDirectionForLocale(const char* locale_name) {
  182. // Check for direction forcing.
  183. TextDirection forced_direction = GetForcedTextDirection();
  184. if (forced_direction != UNKNOWN_DIRECTION)
  185. return forced_direction;
  186. UErrorCode status = U_ZERO_ERROR;
  187. ULayoutType layout_dir = uloc_getCharacterOrientation(locale_name, &status);
  188. DCHECK(U_SUCCESS(status));
  189. // Treat anything other than RTL as LTR.
  190. return (layout_dir != ULOC_LAYOUT_RTL) ? LEFT_TO_RIGHT : RIGHT_TO_LEFT;
  191. }
  192. TextDirection GetFirstStrongCharacterDirection(const std::u16string& text) {
  193. const char16_t* string = text.c_str();
  194. size_t length = text.length();
  195. size_t position = 0;
  196. while (position < length) {
  197. UChar32 character;
  198. size_t next_position = position;
  199. U16_NEXT(string, next_position, length, character);
  200. TextDirection direction = GetCharacterDirection(character);
  201. if (direction != UNKNOWN_DIRECTION)
  202. return direction;
  203. position = next_position;
  204. }
  205. return LEFT_TO_RIGHT;
  206. }
  207. TextDirection GetLastStrongCharacterDirection(const std::u16string& text) {
  208. const char16_t* string = text.c_str();
  209. size_t position = text.length();
  210. while (position > 0) {
  211. UChar32 character;
  212. size_t prev_position = position;
  213. U16_PREV(string, 0, prev_position, character);
  214. TextDirection direction = GetCharacterDirection(character);
  215. if (direction != UNKNOWN_DIRECTION)
  216. return direction;
  217. position = prev_position;
  218. }
  219. return LEFT_TO_RIGHT;
  220. }
  221. TextDirection GetStringDirection(const std::u16string& text) {
  222. const char16_t* string = text.c_str();
  223. size_t length = text.length();
  224. size_t position = 0;
  225. TextDirection result(UNKNOWN_DIRECTION);
  226. while (position < length) {
  227. UChar32 character;
  228. size_t next_position = position;
  229. U16_NEXT(string, next_position, length, character);
  230. TextDirection direction = GetCharacterDirection(character);
  231. if (direction != UNKNOWN_DIRECTION) {
  232. if (result != UNKNOWN_DIRECTION && result != direction)
  233. return UNKNOWN_DIRECTION;
  234. result = direction;
  235. }
  236. position = next_position;
  237. }
  238. // Handle the case of a string not containing any strong directionality
  239. // characters defaulting to LEFT_TO_RIGHT.
  240. if (result == UNKNOWN_DIRECTION)
  241. return LEFT_TO_RIGHT;
  242. return result;
  243. }
  244. #if BUILDFLAG(IS_WIN)
  245. bool AdjustStringForLocaleDirection(std::u16string* text) {
  246. if (!IsRTL() || text->empty())
  247. return false;
  248. // Marking the string as LTR if the locale is RTL and the string does not
  249. // contain strong RTL characters. Otherwise, mark the string as RTL.
  250. bool has_rtl_chars = StringContainsStrongRTLChars(*text);
  251. if (!has_rtl_chars)
  252. WrapStringWithLTRFormatting(text);
  253. else
  254. WrapStringWithRTLFormatting(text);
  255. return true;
  256. }
  257. bool UnadjustStringForLocaleDirection(std::u16string* text) {
  258. if (!IsRTL() || text->empty())
  259. return false;
  260. *text = StripWrappingBidiControlCharacters(*text);
  261. return true;
  262. }
  263. #else
  264. bool AdjustStringForLocaleDirection(std::u16string* text) {
  265. // On OS X & GTK the directionality of a label is determined by the first
  266. // strongly directional character.
  267. // However, we want to make sure that in an LTR-language-UI all strings are
  268. // left aligned and vice versa.
  269. // A problem can arise if we display a string which starts with user input.
  270. // User input may be of the opposite directionality to the UI. So the whole
  271. // string will be displayed in the opposite directionality, e.g. if we want to
  272. // display in an LTR UI [such as US English]:
  273. //
  274. // EMAN_NOISNETXE is now installed.
  275. //
  276. // Since EXTENSION_NAME begins with a strong RTL char, the label's
  277. // directionality will be set to RTL and the string will be displayed visually
  278. // as:
  279. //
  280. // .is now installed EMAN_NOISNETXE
  281. //
  282. // In order to solve this issue, we prepend an LRM to the string. An LRM is a
  283. // strongly directional LTR char.
  284. // We also append an LRM at the end, which ensures that we're in an LTR
  285. // context.
  286. // Unlike Windows, Linux and OS X can correctly display RTL glyphs out of the
  287. // box so there is no issue with displaying zero-width bidi control characters
  288. // on any system. Thus no need for the !IsRTL() check here.
  289. if (text->empty())
  290. return false;
  291. bool ui_direction_is_rtl = IsRTL();
  292. bool has_rtl_chars = StringContainsStrongRTLChars(*text);
  293. if (!ui_direction_is_rtl && has_rtl_chars) {
  294. WrapStringWithRTLFormatting(text);
  295. text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
  296. kLeftToRightMark);
  297. text->push_back(kLeftToRightMark);
  298. } else if (ui_direction_is_rtl && has_rtl_chars) {
  299. WrapStringWithRTLFormatting(text);
  300. text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
  301. kRightToLeftMark);
  302. text->push_back(kRightToLeftMark);
  303. } else if (ui_direction_is_rtl) {
  304. WrapStringWithLTRFormatting(text);
  305. text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
  306. kRightToLeftMark);
  307. text->push_back(kRightToLeftMark);
  308. } else {
  309. return false;
  310. }
  311. return true;
  312. }
  313. bool UnadjustStringForLocaleDirection(std::u16string* text) {
  314. if (text->empty())
  315. return false;
  316. size_t begin_index = 0;
  317. char16_t begin = text->at(begin_index);
  318. if (begin == kLeftToRightMark ||
  319. begin == kRightToLeftMark) {
  320. ++begin_index;
  321. }
  322. size_t end_index = text->length() - 1;
  323. char16_t end = text->at(end_index);
  324. if (end == kLeftToRightMark ||
  325. end == kRightToLeftMark) {
  326. --end_index;
  327. }
  328. std::u16string unmarked_text =
  329. text->substr(begin_index, end_index - begin_index + 1);
  330. *text = StripWrappingBidiControlCharacters(unmarked_text);
  331. return true;
  332. }
  333. #endif // !BUILDFLAG(IS_WIN)
  334. void EnsureTerminatedDirectionalFormatting(std::u16string* text) {
  335. int count = 0;
  336. for (auto c : *text) {
  337. if (c == kLeftToRightEmbeddingMark || c == kRightToLeftEmbeddingMark ||
  338. c == kLeftToRightOverride || c == kRightToLeftOverride) {
  339. ++count;
  340. } else if (c == kPopDirectionalFormatting && count > 0) {
  341. --count;
  342. }
  343. }
  344. for (int j = 0; j < count; j++)
  345. text->push_back(kPopDirectionalFormatting);
  346. }
  347. void SanitizeUserSuppliedString(std::u16string* text) {
  348. EnsureTerminatedDirectionalFormatting(text);
  349. AdjustStringForLocaleDirection(text);
  350. }
  351. bool StringContainsStrongRTLChars(const std::u16string& text) {
  352. const char16_t* string = text.c_str();
  353. size_t length = text.length();
  354. size_t position = 0;
  355. while (position < length) {
  356. UChar32 character;
  357. size_t next_position = position;
  358. U16_NEXT(string, next_position, length, character);
  359. // Now that we have the character, we use ICU in order to query for the
  360. // appropriate Unicode BiDi character type.
  361. int32_t property = u_getIntPropertyValue(character, UCHAR_BIDI_CLASS);
  362. if ((property == U_RIGHT_TO_LEFT) || (property == U_RIGHT_TO_LEFT_ARABIC))
  363. return true;
  364. position = next_position;
  365. }
  366. return false;
  367. }
  368. void WrapStringWithLTRFormatting(std::u16string* text) {
  369. if (text->empty())
  370. return;
  371. // Inserting an LRE (Left-To-Right Embedding) mark as the first character.
  372. text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
  373. kLeftToRightEmbeddingMark);
  374. // Inserting a PDF (Pop Directional Formatting) mark as the last character.
  375. text->push_back(kPopDirectionalFormatting);
  376. }
  377. void WrapStringWithRTLFormatting(std::u16string* text) {
  378. if (text->empty())
  379. return;
  380. // Inserting an RLE (Right-To-Left Embedding) mark as the first character.
  381. text->insert(static_cast<size_t>(0), static_cast<size_t>(1),
  382. kRightToLeftEmbeddingMark);
  383. // Inserting a PDF (Pop Directional Formatting) mark as the last character.
  384. text->push_back(kPopDirectionalFormatting);
  385. }
  386. void WrapPathWithLTRFormatting(const FilePath& path,
  387. std::u16string* rtl_safe_path) {
  388. // Wrap the overall path with LRE-PDF pair which essentialy marks the
  389. // string as a Left-To-Right string.
  390. // Inserting an LRE (Left-To-Right Embedding) mark as the first character.
  391. rtl_safe_path->push_back(kLeftToRightEmbeddingMark);
  392. #if BUILDFLAG(IS_APPLE)
  393. rtl_safe_path->append(UTF8ToUTF16(path.value()));
  394. #elif BUILDFLAG(IS_WIN)
  395. rtl_safe_path->append(AsString16(path.value()));
  396. #else // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_APPLE)
  397. std::wstring wide_path = base::SysNativeMBToWide(path.value());
  398. rtl_safe_path->append(WideToUTF16(wide_path));
  399. #endif
  400. // Inserting a PDF (Pop Directional Formatting) mark as the last character.
  401. rtl_safe_path->push_back(kPopDirectionalFormatting);
  402. }
  403. std::u16string GetDisplayStringInLTRDirectionality(const std::u16string& text) {
  404. // Always wrap the string in RTL UI (it may be appended to RTL string).
  405. // Also wrap strings with an RTL first strong character direction in LTR UI.
  406. if (IsRTL() || GetFirstStrongCharacterDirection(text) == RIGHT_TO_LEFT) {
  407. std::u16string text_mutable(text);
  408. WrapStringWithLTRFormatting(&text_mutable);
  409. return text_mutable;
  410. }
  411. return text;
  412. }
  413. std::u16string StripWrappingBidiControlCharacters(const std::u16string& text) {
  414. if (text.empty())
  415. return text;
  416. size_t begin_index = 0;
  417. char16_t begin = text[begin_index];
  418. if (begin == kLeftToRightEmbeddingMark ||
  419. begin == kRightToLeftEmbeddingMark ||
  420. begin == kLeftToRightOverride ||
  421. begin == kRightToLeftOverride)
  422. ++begin_index;
  423. size_t end_index = text.length() - 1;
  424. if (text[end_index] == kPopDirectionalFormatting)
  425. --end_index;
  426. return text.substr(begin_index, end_index - begin_index + 1);
  427. }
  428. } // namespace i18n
  429. } // namespace base