mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-13 09:39:33 +01:00
Merge pull request #22991 from hrydgard/rtl-text
Lay out right-to-left text consistently across text drawers, fix fa_IR/ar_AE strings
This commit is contained in:
10 files changed
+816
-747
No files matched your search
@@ -169,6 +169,24 @@ bool UTF8StringHasNonASCII(std::string_view utf8string) {
|
||||
return UTF8StringNonASCIICount(utf8string) > 0;
|
||||
}
|
||||
|
||||
static bool CodepointIsRTL(uint32_t c) {
|
||||
return (c >= 0x0590 && c <= 0x08FF) || // Hebrew, Arabic, Syriac, Thaana, NKo, ...
|
||||
(c >= 0xFB1D && c <= 0xFDFF) || // Hebrew and Arabic presentation forms
|
||||
(c >= 0xFE70 && c <= 0xFEFF) ||
|
||||
(c >= 0x10800 && c <= 0x10FFF) ||
|
||||
(c >= 0x1E800 && c <= 0x1EFFF);
|
||||
}
|
||||
|
||||
bool UTF8StringHasRTL(std::string_view utf8string) {
|
||||
UTF8 utf(utf8string);
|
||||
while (!utf.end()) {
|
||||
if (CodepointIsRTL(utf.next())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
std::string ConvertWStringToUTF8(const wchar_t *wstr) {
|
||||
|
||||
@@ -92,6 +92,8 @@ private:
|
||||
int UTF8StringNonASCIICount(std::string_view utf8string);
|
||||
|
||||
bool UTF8StringHasNonASCII(std::string_view utf8string);
|
||||
// True if the string contains a character from a right-to-left script (Hebrew, Arabic, ...).
|
||||
bool UTF8StringHasRTL(std::string_view utf8string);
|
||||
|
||||
|
||||
// Removes overlong encodings and similar.
|
||||
|
||||
@@ -50,6 +50,10 @@ std::string I18NRepo::LanguageID() {
|
||||
return languageID_;
|
||||
}
|
||||
|
||||
bool I18NRepo::IsRTL() const {
|
||||
return languageID_ == "he_IL" || languageID_ == "ar_AE" || languageID_ == "fa_IR" || languageID_ == "ku_SO";
|
||||
}
|
||||
|
||||
I18NRepo::I18NRepo() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
@@ -108,6 +108,8 @@ public:
|
||||
bool LoadIni(const std::string &languageID, const Path &overridePath = Path()); // NOT the filename!
|
||||
|
||||
std::string LanguageID();
|
||||
// The loaded language is written right to left.
|
||||
bool IsRTL() const;
|
||||
|
||||
std::shared_ptr<I18NCategory> GetCategory(I18NCat category);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "Common/System/Display.h"
|
||||
#include "Common/GPU/thin3d.h"
|
||||
#include "Common/Data/Hash/Hash.h"
|
||||
#include "Common/Data/Text/I18n.h"
|
||||
#include "Common/Data/Text/WrapText.h"
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
|
||||
@@ -90,6 +91,33 @@ const std::string &TextDrawer::WrapString(const FontStyle &style, float scale, s
|
||||
return entry.wrapped;
|
||||
}
|
||||
|
||||
// The backends disagree on a line's base direction: GDI and DirectWrite always start left to right, the others
|
||||
// go by the first strong character, which is wrong for a Hebrew sentence that starts with "PPSSPP". An explicit
|
||||
// embedding (RLE ... PDF) means the same to all of them.
|
||||
std::string_view TextDrawer::ApplyBaseDirection(std::string_view str, std::string *storage) {
|
||||
if (!g_i18nrepo.IsRTL() || !UTF8StringHasRTL(str)) {
|
||||
return str;
|
||||
}
|
||||
static constexpr std::string_view RLE = "\xE2\x80\xAB"; // U+202B
|
||||
static constexpr std::string_view PDF = "\xE2\x80\xAC"; // U+202C
|
||||
storage->clear();
|
||||
std::vector<std::string_view> lines;
|
||||
SplitString(str, '\n', lines);
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
if (i > 0) {
|
||||
storage->push_back('\n');
|
||||
}
|
||||
if (UTF8StringHasRTL(lines[i])) {
|
||||
storage->append(RLE);
|
||||
storage->append(lines[i]);
|
||||
storage->append(PDF);
|
||||
} else {
|
||||
storage->append(lines[i]);
|
||||
}
|
||||
}
|
||||
return *storage;
|
||||
}
|
||||
|
||||
float TextDrawer::CalculateDPIScale() const {
|
||||
if (ignoreGlobalDpi_)
|
||||
return dpiScale_;
|
||||
@@ -143,7 +171,8 @@ void TextDrawer::DrawString(DrawBuffer &target, const FontStyle &style, float sc
|
||||
TextureDesc desc{};
|
||||
std::vector<uint8_t> bitmapData;
|
||||
EnsureFont(style);
|
||||
if (!DrawStringBitmap(style, bitmapData, *entry, bitmapFormat, str, align, emoji)) {
|
||||
std::string bidiStorage;
|
||||
if (!DrawStringBitmap(style, bitmapData, *entry, bitmapFormat, ApplyBaseDirection(str, &bidiStorage), align, emoji)) {
|
||||
// Nothing drawn. The entry without a texture records that fact.
|
||||
return;
|
||||
}
|
||||
@@ -199,7 +228,8 @@ void TextDrawer::MeasureString(const FontStyle &style, float scaleX, float scale
|
||||
if (inserted) {
|
||||
EnsureFont(style);
|
||||
float extW = 0.0f, extH = 0.0f;
|
||||
MeasureStringInternal(style, str, &extW, &extH);
|
||||
std::string bidiStorage;
|
||||
MeasureStringInternal(style, ApplyBaseDirection(str, &bidiStorage), &extW, &extH);
|
||||
entry.width = extW;
|
||||
entry.height = extH;
|
||||
// Hm, use the old calculation?
|
||||
@@ -264,7 +294,9 @@ bool TextDrawer::DrawStringBitmapRect(const FontStyle &style, float scale, std::
|
||||
|
||||
const int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
|
||||
EnsureFont(style);
|
||||
return DrawStringBitmap(style, bitmapData, entry, texFormat, wrap ? std::string_view(WrapString(style, scale, str, bounds.w, wrap)) : str, align, fullColor);
|
||||
std::string bidiStorage;
|
||||
const std::string_view text = wrap ? std::string_view(WrapString(style, scale, str, bounds.w, wrap)) : str;
|
||||
return DrawStringBitmap(style, bitmapData, entry, texFormat, ApplyBaseDirection(text, &bidiStorage), align, fullColor);
|
||||
}
|
||||
|
||||
void TextDrawer::AddDropShadow(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat srcFormat) const {
|
||||
|
||||
@@ -114,6 +114,10 @@ protected:
|
||||
// Wraps and/or ellipsizes, caching the result. The reference stays valid until the next OncePerFrame.
|
||||
const std::string &WrapString(const FontStyle &style, float scale, std::string_view str, float maxWidth, int flags);
|
||||
|
||||
// When the UI language is right to left, puts each line containing right-to-left text in an RTL embedding,
|
||||
// so every backend lays it out right to left, whatever its first character. Otherwise returns str as is.
|
||||
static std::string_view ApplyBaseDirection(std::string_view str, std::string *storage);
|
||||
|
||||
// v has the 8-bit alpha.
|
||||
static u16 AlphaToPremul4444(u32 v) {
|
||||
v = (v >> 4) & 0x0F;
|
||||
|
||||
@@ -58,6 +58,15 @@ static bool ReorderBidi(std::string_view str, std::string *out) {
|
||||
result.push_back('\n');
|
||||
}
|
||||
std::string_view line = lines[i];
|
||||
FriBidiParType baseDir = FRIBIDI_PAR_ON;
|
||||
// TextDrawer::ApplyBaseDirection wraps RTL lines in RLE ... PDF. Take the direction from that instead,
|
||||
// so the marks don't count as a second level and send pure Arabic lines down the reordering path.
|
||||
const std::string_view rle = "\xE2\x80\xAB";
|
||||
const std::string_view pdf = "\xE2\x80\xAC";
|
||||
if (line.size() >= rle.size() + pdf.size() && startsWith(line, rle) && endsWith(line, pdf)) {
|
||||
line = line.substr(rle.size(), line.size() - rle.size() - pdf.size());
|
||||
baseDir = FRIBIDI_PAR_RTL;
|
||||
}
|
||||
logical.resize(line.size() + 1);
|
||||
FriBidiStrIndex len = fribidi_charset_to_unicode(FRIBIDI_CHAR_SET_UTF8, line.data(), (FriBidiStrIndex)line.size(), logical.data());
|
||||
if (len <= 0) {
|
||||
@@ -66,7 +75,6 @@ static bool ReorderBidi(std::string_view str, std::string *out) {
|
||||
}
|
||||
visual.resize(len + 1);
|
||||
levels.resize(len);
|
||||
FriBidiParType baseDir = FRIBIDI_PAR_ON;
|
||||
if (!fribidi_log2vis(logical.data(), len, &baseDir, visual.data(), nullptr, nullptr, levels.data())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+2
-3
@@ -304,9 +304,8 @@ NewLanguageScreen::NewLanguageScreen(std::string_view title) : ListPopupScreen(t
|
||||
continue;
|
||||
}
|
||||
|
||||
// We only support Arabic on platforms where we have support for the native text rendering
|
||||
// APIs, as proper Arabic support is way too difficult to implement ourselves.
|
||||
#if !(PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID))
|
||||
// Arabic script needs a text drawer that does shaping (see TextDrawer::Create), the atlas font can't.
|
||||
#if defined(__LIBRETRO__) || !(PPSSPP_PLATFORM(WINDOWS) || PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) || defined(USE_SDL3_TTF))
|
||||
if (tempLangs[i].name.find("ar_AE") != std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+425
-425
File diff suppressed because it is too large.
Load diff
+315
-315
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user