# HG changeset patch # User rubidium # Date 2008-10-17 17:14:09 # Node ID 82b5afcc36bb3fc72caec45323675f57c7478819 # Parent 46565a9450cbbf5a0c83397227cd973eacfc8709 (svn r14479) -Add: initial (optional) support for handling bidirectional scripts and connecting Arabic characters. diff --git a/config.lib b/config.lib --- a/config.lib +++ b/config.lib @@ -69,6 +69,7 @@ set_default() { with_libtimidity="1" with_freetype="1" with_fontconfig="1" + with_icu="1" with_psp_config="1" with_threads="1" with_distcc="1" @@ -133,6 +134,7 @@ set_default() { with_libtimidity with_freetype with_fontconfig + with_icu with_psp_config with_threads with_distcc @@ -320,6 +322,13 @@ detect_params() { --without-libfontconfig) with_fontconfig="0";; --with-libfontconfig=*) with_fontconfig="$optarg";; + --with-icu) with_icu="2";; + --without-icu) with_icu="0";; + --with-icu=*) with_icu="$optarg";; + --with-libicu) with_icu="2";; + --without-libicu) with_icu="0";; + --with-libicu=*) with_icu="$optarg";; + --with-psp-config) with_psp_config="2";; --without-psp-config) with_psp_config="0";; --with-psp-config=*) with_psp_config="$optarg";; @@ -604,6 +613,7 @@ check_params() { detect_png detect_freetype detect_fontconfig + detect_icu detect_pspconfig detect_libtimidity @@ -1227,6 +1237,14 @@ make_cflags_and_ldflags() { fi fi + if [ -n "$icu_config" ]; then + CFLAGS="$CFLAGS -DWITH_ICU" + CFLAGS="$CFLAGS `$icu_config --cppflags | tr '\n\r' ' '`" + + LIBS="$LIBS `$icu_config --ldflags-libsonly | tr '\n\r' ' '`" + fi + + if [ "$with_direct_music" != "0" ]; then CFLAGS="$CFLAGS -DWIN32_ENABLE_DIRECTMUSIC_SUPPORT" # GCC 4.0+ doesn't like the DirectX includes (gives tons of @@ -2060,6 +2078,49 @@ detect_fontconfig() { log 1 "checking libfontconfig... found" } +detect_icu() { + # 0 means no, 1 is auto-detect, 2 is force + if [ "$with_icu" = "0" ]; then + log 1 "checking libicu... disabled" + + icu_config="" + return 0 + fi + + if [ "$with_icu" = "1" ] || [ "$with_icu" = "" ] || [ "$with_icu" = "2" ]; then + icu_config="icu-config" + else + icu_config="$with_icu" + fi + + version=`$icu_config --version 2>/dev/null` + ret=$? + shortversion=`echo $version | cut -c 1,3` + log 2 "executing $icu_config --version" + log 2 " returned $version" + log 2 " exit code $ret" + + if [ -z "$version" ] || [ "$ret" != "0" ] || [ "$shortversion" -lt "20" ]; then + if [ -n "$shortversion" ] && [ "$shortversion" -lt "20" ]; then + log 1 "checking libicu... needs at least version 2.0.0, icu NOT enabled" + else + log 1 "checking libicu... not found" + fi + + # It was forced, so it should be found. + if [ "$with_icu" != "1" ]; then + log 1 "configure: error: icu-config couldn't be found" + log 1 "configure: error: you supplied '$with_icuconfig', but it seems invalid" + exit 1 + fi + + icu_config="" + return 0 + fi + + log 1 "checking libicu... found" +} + detect_pspconfig() { # 0 means no, 1 is auto-detect, 2 is force if [ "$with_psp_config" = "0" ]; then diff --git a/src/gfx.cpp b/src/gfx.cpp --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -51,6 +51,7 @@ DrawPixelInfo *_cur_dpi; byte _colour_gradient[COLOUR_END][8]; static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL); +static int ReallyDoDrawString(const char *string, int x, int y, uint16 real_colour, bool parse_string_also_when_clipped = false); FontSize _cur_fontsize; static FontSize _last_fontsize; @@ -238,6 +239,74 @@ void DrawBox(int x, int y, int dx1, int } +#if !defined(WITH_ICU) +static void HandleBiDiAndArabicShapes(char *text, const char *lastof) {} +#else +#include "unicode/ubidi.h" +#include "unicode/ushape.h" + +/** + * Function to be able to handle right-to-left text and Arabic chars properly. + * + * First: right-to-left (RTL) is stored 'logically' in almost all applications + * and so do we. This means that their text is stored from right to the + * left in memory and any non-RTL text (like numbers or English) are + * then stored from left-to-right. When we want to actually draw the + * text we need to reverse the RTL text in memory, which is what + * happens in ubidi_writeReordered. + * Second: Arabic characters "differ" based on their context. To draw the + * correct variant we pass it through u_shapeArabic. This function can + * add or remove some characters. This is the reason for the lastof + * so we know till where we can fill the output. + * + * Sadly enough these functions work with a custom character format, UChar, + * which isn't the same size as WChar. Because of that we need to transform + * our text first to UChars and then back to something we can use. + * + * To be able to truncate strings properly you must truncate before passing to + * this function. This way the logical begin of the string remains and the end + * gets chopped of instead of the other way around. + * + * The reshaping of Arabic characters might increase or decrease the width of + * the characters/string. So it might still overflow after truncation, though + * the chance is fairly slim as most characters get shorter instead of longer. + * @param buffer the buffer to read from/to + * @param lastof the end of the buffer + */ +static void HandleBiDiAndArabicShapes(char *buffer, const char *lastof) +{ + UChar input_output[DRAW_STRING_BUFFER]; + UChar intermediate[DRAW_STRING_BUFFER]; + + char *t = buffer; + size_t length = 0; + while (*t != '\0' && length < lengthof(input_output)) { + WChar tmp; + t += Utf8Decode(&tmp, t); + input_output[length++] = tmp; + } + input_output[length] = 0; + + UErrorCode err = U_ZERO_ERROR; + UBiDi *para = ubidi_openSized(length, 0, &err); + if (para == NULL) return; + + ubidi_setPara(para, input_output, length, UBIDI_DEFAULT_RTL, NULL, &err); + ubidi_writeReordered(para, intermediate, length, 0, &err); + length = u_shapeArabic(intermediate, length, input_output, lengthof(input_output), U_SHAPE_TEXT_DIRECTION_VISUAL_LTR | U_SHAPE_LETTERS_SHAPE, &err); + ubidi_close(para); + + if (U_FAILURE(err)) return; + + t = buffer; + for (size_t i = 0; i < length && t < (lastof - 4); i++) { + t += Utf8Encode(t, input_output[i]); + } + *t = '\0'; +} +#endif /* WITH_ICU */ + + /** Truncate a given string to a maximum width if neccessary. * If the string is truncated, add three dots ('...') to show this. * @param *str string that is checked and possibly truncated @@ -322,7 +391,8 @@ int DrawString(int x, int y, StringID st char buffer[DRAW_STRING_BUFFER]; GetString(buffer, str, lastof(buffer)); - return DoDrawString(buffer, x, y, color); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); + return ReallyDoDrawString(buffer, x, y, color); } /** @@ -340,7 +410,8 @@ int DrawStringTruncated(int x, int y, St { char buffer[DRAW_STRING_BUFFER]; TruncateStringID(str, buffer, maxw, lastof(buffer)); - return DoDrawString(buffer, x, y, color); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); + return ReallyDoDrawString(buffer, x, y, color); } /** @@ -359,8 +430,10 @@ int DrawStringRightAligned(int x, int y, int w; GetString(buffer, str, lastof(buffer)); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); + w = GetStringBoundingBox(buffer).width; - DoDrawString(buffer, x - w, y, color); + ReallyDoDrawString(buffer, x - w, y, color); return w; } @@ -379,7 +452,8 @@ void DrawStringRightAlignedTruncated(int char buffer[DRAW_STRING_BUFFER]; TruncateStringID(str, buffer, maxw, lastof(buffer)); - DoDrawString(buffer, x - GetStringBoundingBox(buffer).width, y, color); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); + ReallyDoDrawString(buffer, x - GetStringBoundingBox(buffer).width, y, color); } /** @@ -412,9 +486,10 @@ int DrawStringCentered(int x, int y, Str int w; GetString(buffer, str, lastof(buffer)); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); w = GetStringBoundingBox(buffer).width; - DoDrawString(buffer, x - w / 2, y, color); + ReallyDoDrawString(buffer, x - w / 2, y, color); return w; } @@ -433,8 +508,11 @@ int DrawStringCentered(int x, int y, Str int DrawStringCenteredTruncated(int xl, int xr, int y, StringID str, uint16 color) { char buffer[DRAW_STRING_BUFFER]; - int w = TruncateStringID(str, buffer, xr - xl, lastof(buffer)); - return DoDrawString(buffer, (xl + xr - w) / 2, y, color); + TruncateStringID(str, buffer, xr - xl, lastof(buffer)); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); + + int w = GetStringBoundingBox(buffer).width; + return ReallyDoDrawString(buffer, (xl + xr - w) / 2, y, color); } /** @@ -449,8 +527,12 @@ int DrawStringCenteredTruncated(int xl, */ int DoDrawStringCentered(int x, int y, const char *str, uint16 color) { - int w = GetStringBoundingBox(str).width; - DoDrawString(str, x - w / 2, y, color); + char buffer[DRAW_STRING_BUFFER]; + strecpy(buffer, str, lastof(buffer)); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); + + int w = GetStringBoundingBox(buffer).width; + ReallyDoDrawString(buffer, x - w / 2, y, color); return w; } @@ -613,7 +695,7 @@ void DrawStringMultiCenter(int x, int y, { char buffer[DRAW_STRING_BUFFER]; uint32 tmp; - int num, w, mt; + int num, mt; const char *src; WChar c; @@ -629,8 +711,11 @@ void DrawStringMultiCenter(int x, int y, src = buffer; for (;;) { - w = GetStringBoundingBox(src).width; - DoDrawString(src, x - (w >> 1), y, 0xFE, true); + char buf2[DRAW_STRING_BUFFER]; + strecpy(buf2, src, lastof(buf2)); + HandleBiDiAndArabicShapes(buf2, lastof(buf2)); + int w = GetStringBoundingBox(buf2).width; + ReallyDoDrawString(buf2, x - (w >> 1), y, 0xFE, true); _cur_fontsize = _last_fontsize; for (;;) { @@ -680,7 +765,10 @@ uint DrawStringMultiLine(int x, int y, S src = buffer; for (;;) { - DoDrawString(src, x, y, 0xFE, true); + char buf2[DRAW_STRING_BUFFER]; + strecpy(buf2, src, lastof(buf2)); + HandleBiDiAndArabicShapes(buf2, lastof(buf2)); + ReallyDoDrawString(buf2, x, y, 0xFE, true); _cur_fontsize = _last_fontsize; for (;;) { @@ -767,7 +855,7 @@ void DrawCharCentered(WChar c, int x, in /** Draw a string at the given coordinates with the given colour. * While drawing the string, parse it in case some formatting is specified, * like new colour, new size or even positionning. - * @param string The string to draw + * @param string The string to draw. This is not yet bidi reordered. * @param x Offset from left side of the screen * @param y Offset from top side of the screen * @param real_colour Colour of the string, see _string_colormap in @@ -783,6 +871,32 @@ void DrawCharCentered(WChar c, int x, in */ int DoDrawString(const char *string, int x, int y, uint16 real_colour, bool parse_string_also_when_clipped) { + char buffer[DRAW_STRING_BUFFER]; + strecpy(buffer, string, lastof(buffer)); + HandleBiDiAndArabicShapes(buffer, lastof(buffer)); + + return ReallyDoDrawString(buffer, x, y, real_colour, parse_string_also_when_clipped); +} + +/** Draw a string at the given coordinates with the given colour. + * While drawing the string, parse it in case some formatting is specified, + * like new colour, new size or even positionning. + * @param string The string to draw. This is already bidi reordered. + * @param x Offset from left side of the screen + * @param y Offset from top side of the screen + * @param real_colour Colour of the string, see _string_colormap in + * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h + * @param parse_string_also_when_clipped + * By default, always test the available space where to draw the string. + * When in multipline drawing, it would already be done, + * so no need to re-perform the same kind (more or less) of verifications. + * It's not only an optimisation, it's also a way to ensures the string will be parsed + * (as there are certain side effects on global variables, which are important for the next line) + * @return the x-coordinates where the drawing has finished. + * If nothing is drawn, the originally passed x-coordinate is returned + */ +static int ReallyDoDrawString(const char *string, int x, int y, uint16 real_colour, bool parse_string_also_when_clipped) +{ DrawPixelInfo *dpi = _cur_dpi; FontSize size = _cur_fontsize; WChar c;