(svn r14479) -Add: initial (optional) support for handling bidirectional scripts and connecting Arabic characters.
authorrubidium
Fri, 17 Oct 2008 17:14:09 +0000
changeset 10248 f0297aac2ff2
parent 10247 b4bed389d825
child 10249 77b68778b102
(svn r14479) -Add: initial (optional) support for handling bidirectional scripts and connecting Arabic characters.
config.lib
src/gfx.cpp
--- a/config.lib	Thu Oct 16 13:02:15 2008 +0000
+++ b/config.lib	Fri Oct 17 17:14:09 2008 +0000
@@ -69,6 +69,7 @@
 	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 @@
 		with_libtimidity
 		with_freetype
 		with_fontconfig
+		with_icu
 		with_psp_config
 		with_threads
 		with_distcc
@@ -320,6 +322,13 @@
 			--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 @@
 	detect_png
 	detect_freetype
 	detect_fontconfig
+	detect_icu
 	detect_pspconfig
 	detect_libtimidity
 
@@ -1227,6 +1237,14 @@
 		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 @@
 	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
--- a/src/gfx.cpp	Thu Oct 16 13:02:15 2008 +0000
+++ b/src/gfx.cpp	Fri Oct 17 17:14:09 2008 +0000
@@ -51,6 +51,7 @@
 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 @@
 }
 
 
+#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 @@
 	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 @@
 {
 	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 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 @@
 	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 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 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 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 @@
 {
 	char buffer[DRAW_STRING_BUFFER];
 	uint32 tmp;
-	int num, w, mt;
+	int num, mt;
 	const char *src;
 	WChar c;
 
@@ -629,8 +711,11 @@
 	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 @@
 	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 @@
 /** 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 @@
  */
 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;