Add RTL level property and print flag

Again, the RTL property controls whether textboxes will be
right-aligned, and that kind of stuff. It can't be font-bound, since
Space Station supports Hebrew characters and we want to be able to
support, say, a Hebrew translation or Hebrew levels in the future
without having to make a dedicated (or duplicated) font for it.
Therefore it's a property of both the language pack as well as custom
levels - like custom levels already had a <font> tag, they now also
have an <rtl> tag that sets this property.

Right now, we'll have to hardcode it so the menu option for the Arabic
font sets the <rtl> property to 1, and all the other options set it to
0. But it's future-proof in that we can later decide to split the
option for Space Station into an LTR option and an RTL option (so both
"english/..." and "עברית" would select Space Station, but one sets the
RTL property to 0 and the other sets it to 1).
This commit is contained in:
Dav999 2024-01-03 20:09:23 +01:00 committed by Misa Elizabeth Kai
parent 37c4f76988
commit 29e2b19698
11 changed files with 83 additions and 20 deletions

View File

@ -227,6 +227,7 @@ TAG_FINDER(find_desc2, "Desc2")
TAG_FINDER(find_desc3, "Desc3")
TAG_FINDER(find_website, "website")
TAG_FINDER(find_font, "font")
TAG_FINDER(find_rtl, "rtl")
/* For CliPlaytestArgs */
TAG_FINDER(find_playtest, "Playtest")
@ -315,6 +316,7 @@ bool customlevelclass::getLevelMetaDataAndPlaytestArgs(const std::string& _path,
{
_data.level_main_font_idx = font::get_font_idx_8x8();
}
_data.rtl = help.Int(find_rtl(buf).c_str());
if (pt_args != NULL)
@ -1037,6 +1039,7 @@ bool customlevelclass::load(std::string _path)
version = 0;
level_font_name = "font";
rtl = false;
for (pElem = hDoc
.FirstChildElement()
@ -1104,6 +1107,11 @@ bool customlevelclass::load(std::string _path)
{
level_font_name = pText_;
}
if(SDL_strcmp(pKey_, "rtl") == 0)
{
rtl = help.Int(pText_);
}
}
}
@ -1524,6 +1532,20 @@ bool customlevelclass::save(const std::string& _path)
}
}
if (rtl)
{
xml::update_tag(msg, "rtl", rtl);
}
else
{
// Also get rid of this one...
tinyxml2::XMLElement* element;
while ((element = msg->FirstChildElement("rtl")) != NULL)
{
doc.DeleteNode(element);
}
}
xml::update_tag(data, "mapwidth", mapwidth);
xml::update_tag(data, "mapheight", mapheight);

View File

@ -66,6 +66,7 @@ struct LevelMetaData
/* This is for the metadata in the levels list,
* so it will only be a main font (no custom ones). */
uint8_t level_main_font_idx;
bool rtl;
};
struct CliPlaytestArgs
@ -161,6 +162,7 @@ public:
int mapwidth, mapheight; //Actual width and height of stage
std::string level_font_name;
bool rtl;
int version;

View File

@ -85,6 +85,7 @@ struct PrintFlags
bool align_right;
bool cjk_low;
bool cjk_high;
bool rtl;
};
static FontContainer fonts_main = {};
@ -558,6 +559,8 @@ void set_level_font(const char* name)
}
}
}
cl.rtl = SDL_strcmp(name, "font_ar") == 0; // FIXME: make different menu options for choosing LTR/RTL of the same font
}
void set_level_font_interface(void)
@ -590,6 +593,7 @@ void set_level_font_new(void)
}
cl.level_font_name = get_main_font_name(font_idx_level);
cl.rtl = cl.level_font_name == "font_ar"; // FIXME: make different menu options for choosing LTR/RTL of the same font
}
static void fill_map_name_idx(FontContainer* container)
@ -763,13 +767,19 @@ static Font* container_get(FontContainer* container, uint8_t idx)
return NULL;
}
static Font* fontsel_to_font(int sel)
static Font* fontsel_to_font(int sel, bool* rtl)
{
/* Take font selection integer (0-31) and turn it into the correct Font
* 0: PR_FONT_INTERFACE - use interface font
* 1: PR_FONT_LEVEL - use level font
* 2: PR_FONT_8X8 - use 8x8 font no matter what
* 3-31: - use (main) font index 0-28 */
* 3-31: - use (main) font index 0-28
*
* rtl will be set depending on whether we're requesting the interface
* font (take it from the lang attributes) or level font (take it from
* the level attributes), or false otherwise. */
*rtl = false;
if (sel < 0)
{
@ -782,6 +792,7 @@ static Font* fontsel_to_font(int sel)
case 1:
if (!font_level_is_interface)
{
*rtl = cl.rtl;
if (font_idx_level_is_custom)
{
return container_get(&fonts_custom, font_idx_level);
@ -793,6 +804,7 @@ static Font* fontsel_to_font(int sel)
}
SDL_FALLTHROUGH;
case 0:
*rtl = loc::get_langmeta()->rtl;
return container_get(&fonts_main, loc::get_langmeta()->font_idx);
case 2:
return container_get(&fonts_main, font_idx_8x8);
@ -806,7 +818,11 @@ static PrintFlags decode_print_flags(uint32_t flags)
{
PrintFlags pf;
pf.scale = FLAG_PART(0, 3) + 1;
pf.font_sel = fontsel_to_font(FLAG_PART(3, 5));
pf.font_sel = fontsel_to_font(FLAG_PART(3, 5), &pf.rtl);
if (flags & PR_RTL_FORCE)
{
pf.rtl = true;
}
pf.brightness = ~FLAG_PART(8, 8) & 0xff;
pf.border = flags & PR_BOR;
pf.full_border = flags & PR_FULLBOR;
@ -1206,9 +1222,9 @@ int len(const uint32_t flags, const char* text)
{
PrintFlags pf = decode_print_flags(flags);
if (bidi_should_transform(text))
if (bidi_should_transform(pf.rtl, text))
{
text = bidi_transform(text);
text = bidi_transform(pf.rtl, text);
}
int text_len = 0;
@ -1313,9 +1329,9 @@ void print(
y -= h_diff_8/2;
}
if (bidi_should_transform(text))
if (bidi_should_transform(pf.rtl, text))
{
text = bidi_transform(text);
text = bidi_transform(pf.rtl, text);
}
int position = 0;

View File

@ -42,7 +42,8 @@
#define PR_FONT_INTERFACE (0 << 3) /* default, use interface font */
#define PR_FONT_LEVEL (1 << 3) /* use level-specific font (room names, cutscene dialogue, etc) */
#define PR_FONT_8X8 (2 << 3) /* use 8x8 font no matter what */
#define PR_FONT_IDX(idx) ((SDL_clamp(idx, 0, 28) + 3) << 3) /* use given font index */
#define PR_FONT_IDX(idx, rtl) /* use given font index */\
(((SDL_clamp(idx, 0, 28) + 3) << 3) | (rtl ? PR_RTL_FORCE : 0))
#define PR_BRIGHTNESS(value) /* use this brightness 0-255 for the text (accounts for button glyphs correctly) */\
(((~SDL_clamp((int)(value), 0, 255) & 0xff) << 8))
#define PR_FULLBOR (1 << 16) /* draw a black border around the text, filling in the corners (for the map legend) */
@ -53,6 +54,7 @@
#define PR_CJK_CEN (0 << 20) /* default, larger fonts should stick out on top and bottom compared to 8x8 font */
#define PR_CJK_LOW (1 << 20) /* larger fonts should stick out fully on the bottom (draw at Y) */
#define PR_CJK_HIGH (2 << 20) /* larger fonts should stick out fully on the top */
#define PR_RTL_FORCE (1 << 22) /* force the RTL flag, not needed if the font is set to INTERFACE or LEVEL */
namespace font

View File

@ -282,11 +282,16 @@ bool is_joiner(const uint32_t codepoint)
return codepoint == 0x200C || codepoint == 0x200D;
}
bool bidi_should_transform(const char* text)
bool bidi_should_transform(const bool rtl, const char* text)
{
/* Just as an optimization, only run the whole bidi machinery if the
* language is actually an RTL one, _or_ if an RTL character is found. */
if (rtl)
{
return true;
}
const char* text_ptr = text;
uint32_t ch;
while ((ch = UTF8_next(&text_ptr)))
@ -313,7 +318,7 @@ bool bidi_should_transform(const char* text)
return false;
}
const char* bidi_transform(const char* text)
const char* bidi_transform(const bool rtl, const char* text)
{
uint32_t utf32_in[1024];
int n_codepoints = 0;
@ -345,7 +350,12 @@ const char* bidi_transform(const char* text)
{
return text;
}
SBParagraphRef paragraph = SBAlgorithmCreateParagraph(algorithm, 0, INT32_MAX, SBLevelDefaultRTL);
SBParagraphRef paragraph = SBAlgorithmCreateParagraph(
algorithm,
0,
INT32_MAX,
rtl ? SBLevelDefaultRTL : SBLevelDefaultLTR
);
SDL_assert(paragraph != NULL);
SBUInteger paragraph_len = SBParagraphGetLength(paragraph);
SBLineRef paragraph_line = SBParagraphCreateLine(paragraph, 0, paragraph_len);

View File

@ -10,8 +10,8 @@ void bidi_init(void);
void bidi_destroy(void);
bool is_directional_character(uint32_t codepoint);
bool is_joiner(uint32_t codepoint);
bool bidi_should_transform(const char* text);
const char* bidi_transform(const char* text);
bool bidi_should_transform(bool rtl, const char* text);
const char* bidi_transform(bool rtl, const char* text);
} // namespace font

View File

@ -6357,7 +6357,7 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ )
text,
true,
cl.ListOfMetaData[i].title_is_gettext ? PR_FONT_INTERFACE : PR_FONT_IDX(
cl.ListOfMetaData[i].level_main_font_idx
cl.ListOfMetaData[i].level_main_font_idx, cl.ListOfMetaData[i].rtl
)
);
}
@ -6487,7 +6487,7 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ )
for (uint8_t i = 0; i < font::font_idx_options_n; i++)
{
uint8_t idx = font::font_idx_options[i];
option(font::get_main_font_display_name(idx), true, PR_FONT_IDX(idx));
option(font::get_main_font_display_name(idx), true, PR_FONT_IDX(idx, false));
if (font::level_font_is_main_idx(idx))
{
option_match = i;
@ -6586,9 +6586,17 @@ void Game::createmenu( enum Menu::MenuName t, bool samemenu/*= false*/ )
for (size_t i = 0; i < loc::languagelist.size(); i++)
{
if (loc::languagelist[i].nativename.empty())
{
option(loc::languagelist[i].code.c_str());
}
else
option(loc::languagelist[i].nativename.c_str(), true, PR_FONT_IDX(loc::languagelist[i].font_idx));
{
option(
loc::languagelist[i].nativename.c_str(),
true,
PR_FONT_IDX(loc::languagelist[i].font_idx, loc::languagelist[i].rtl)
);
}
}
menuyoff = 70-(menuoptions.size()*10);

View File

@ -64,7 +64,7 @@ static void sync_lang_file(const std::string& langcode)
loadtext(false);
uint8_t glyph_w = 8, glyph_h = 8;
font::glyph_dimensions(PR_FONT_IDX(langmeta.font_idx), &glyph_w, &glyph_h);
font::glyph_dimensions(PR_FONT_IDX(langmeta.font_idx, langmeta.rtl), &glyph_w, &glyph_h);
bool max_local_needed = glyph_w != 8 || glyph_h != 8;
tinyxml2::XMLDocument doc;

View File

@ -311,7 +311,7 @@ static bool max_check_string(const char* str, const char* max)
}
uint8_t font_idx = get_langmeta()->font_idx;
uint32_t print_flags = PR_FONT_IDX(font_idx) | PR_CJK_LOW;
uint32_t print_flags = PR_FONT_IDX(font_idx, get_langmeta()->rtl) | PR_CJK_LOW;
uint8_t font_w = 8;
uint8_t font_h = 8;
font::glyph_dimensions(print_flags, &font_w, &font_h);

View File

@ -266,7 +266,10 @@ static void menurender(void)
}
else
{
uint32_t level_flags = PR_FONT_IDX(cl.ListOfMetaData[tmp].level_main_font_idx);
uint32_t level_flags = PR_FONT_IDX(
cl.ListOfMetaData[tmp].level_main_font_idx,
cl.ListOfMetaData[tmp].rtl
);
uint32_t title_flags = cl.ListOfMetaData[tmp].title_is_gettext ? PR_FONT_INTERFACE : level_flags;
uint32_t creator_flags = cl.ListOfMetaData[tmp].creator_is_gettext ? PR_FONT_INTERFACE : level_flags;

View File

@ -937,7 +937,7 @@ static void unfocused_run(void)
#define FLIP_PR_CJK_HIGH (graphics.flipmode ? PR_CJK_LOW : PR_CJK_HIGH)
/* The pause screen can also appear on the language screen, where highlighting
* a language changes the used language metadata but not the loaded strings... */
uint32_t flags = PR_CEN | PR_BOR | PR_FONT_IDX(loc::langmeta.font_idx);
uint32_t flags = PR_CEN | PR_BOR | PR_FONT_IDX(loc::langmeta.font_idx, loc::langmeta.rtl);
font::print(flags | FLIP_PR_CJK_HIGH, -1, FLIP(110), loc::gettext("Game paused"), 196 - help.glow, 255 - help.glow, 196 - help.glow);
if (BUTTONGLYPHS_keyboard_is_available())