Changeset - r12163:3f9efbd13983
[Not reviewed]
master
0 1 0
rubidium - 15 years ago 2009-06-17 13:04:37
rubidium@openttd.org
(svn r16584) -Fix [FS#2965]: sometimes SETX/SETXY would lead to unexpected results for NewGRF texts.
Note: This readds support for centering strings with SETX, however the text part of the string will not be in the exact center, as the SETX offsets that.
Note: All means of aligning vehicle names behind wide sprites (SETX or lots of spaces) in the buy menu will cause the vehicle names to be misaligned in other places, like the new vehicle news message, exclusive use of vehicle message, detailed vehicle information or autoreplace.
1 file changed with 99 insertions and 49 deletions:
0 comments (0 inline, 0 general)
src/gfx.cpp
Show inline comments
 
@@ -47,13 +47,12 @@ int _pal_count_dirty;
 
Colour _cur_palette[256];
 
byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
 
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, TextColour colour, bool parse_string_also_when_clipped = false);
 

	
 
FontSize _cur_fontsize;
 
static FontSize _last_fontsize;
 
static ReusableBuffer<uint8> _cursor_backup;
 

	
 
/**
 
@@ -253,13 +252,14 @@ static void SetColourRemap(TextColour co
 
		_string_colourremap[2] = _string_colourmap[_use_palette][colour].shadow;
 
	}
 
	_colour_remap_ptr = _string_colourremap;
 
}
 

	
 
#if !defined(WITH_ICU)
 
static void HandleBiDiAndArabicShapes(char *text, const char *lastof) {}
 
typedef WChar UChar;
 
static UChar *HandleBiDiAndArabicShapes(UChar *text) { return text; }
 
#else
 
#include <unicode/ubidi.h>
 
#include <unicode/ushape.h>
 

	
 
/**
 
 * Function to be able to handle right-to-left text and Arabic chars properly.
 
@@ -285,43 +285,39 @@ static void HandleBiDiAndArabicShapes(ch
 
 *
 
 * 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
 
 * @return the buffer to draw from
 
 */
 
static void HandleBiDiAndArabicShapes(char *buffer, const char *lastof)
 
static UChar *HandleBiDiAndArabicShapes(UChar *buffer)
 
{
 
	UChar input_output[DRAW_STRING_BUFFER];
 
	static UChar input_output[DRAW_STRING_BUFFER];
 
	UChar intermediate[DRAW_STRING_BUFFER];
 

	
 
	char *t = buffer;
 
	UChar *t = buffer;
 
	size_t length = 0;
 
	while (*t != '\0' && length < lengthof(input_output) - 1) {
 
		WChar tmp;
 
		t += Utf8Decode(&tmp, t);
 
		input_output[length++] = tmp;
 
		input_output[length++] = *t++;
 
	}
 
	input_output[length] = 0;
 

	
 
	UErrorCode err = U_ZERO_ERROR;
 
	UBiDi *para = ubidi_openSized((int32_t)length, 0, &err);
 
	if (para == NULL) return;
 
	if (para == NULL) return buffer;
 

	
 
	ubidi_setPara(para, input_output, (int32_t)length, _dynlang.text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, &err);
 
	ubidi_writeReordered(para, intermediate, (int32_t)length, 0, &err);
 
	length = u_shapeArabic(intermediate, (int32_t)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;
 
	if (U_FAILURE(err)) return buffer;
 

	
 
	t = buffer;
 
	for (size_t i = 0; i < length && t < (lastof - 4); i++) {
 
		t += Utf8Encode(t, input_output[i]);
 
	}
 
	*t = '\0';
 
	input_output[length] = '\0';
 
	return input_output;
 
}
 
#endif /* WITH_ICU */
 

	
 

	
 
/** Truncate a given string to a maximum width if neccessary.
 
 * If the string is truncated, add three dots ('...') to show this.
 
@@ -376,12 +372,51 @@ static int TruncateString(char *str, int
 
		}
 
	}
 

	
 
	return w;
 
}
 

	
 
static int ReallyDoDrawString(const UChar *string, int x, int y, TextColour &colour, bool parse_string_also_when_clipped = false);
 

	
 
/**
 
 * Get the real width of the string.
 
 * @param str the string to draw
 
 * @return the width.
 
 */
 
static int GetStringWidth(const UChar *str)
 
{
 
	FontSize size = _cur_fontsize;
 
	int max_width;
 
	int width;
 
	WChar c;
 

	
 
	width = max_width = 0;
 
	for (;;) {
 
		c = *str++;
 
		if (c == 0) break;
 
		if (IsPrintable(c)) {
 
			width += GetCharacterWidth(size, c);
 
		} else {
 
			switch (c) {
 
				case SCC_SETX:
 
				case SCC_SETXY:
 
					/* At this point there is no SCC_SETX(Y) anymore */
 
					NOT_REACHED();
 
					break;
 
				case SCC_TINYFONT: size = FS_SMALL; break;
 
				case SCC_BIGFONT:  size = FS_LARGE; break;
 
				case '\n':
 
					max_width = max(max_width, width);
 
					break;
 
			}
 
		}
 
	}
 

	
 
	return max(max_width, width);
 
}
 

	
 
/**
 
 * Draw string, possibly truncated to make it fit in its allocated space
 
 *
 
 * @param left   The left most position to draw on.
 
 * @param right  The right most position to draw on.
 
 * @param top    The top most position to draw on.
 
@@ -411,65 +446,82 @@ static int DrawString(int left, int righ
 
	/*
 
	 * To support SETX and SETXY properly with RTL languages we have to
 
	 * calculate the offsets from the right. To do this we need to split
 
	 * the string and draw the parts separated by SETX(Y).
 
	 * So here we split
 
	 */
 
	static SmallVector<char *, 4> setx_offsets;
 
	static SmallVector<UChar *, 4> setx_offsets;
 
	setx_offsets.Clear();
 
	*setx_offsets.Append() = str;
 

	
 
	UChar draw_buffer[DRAW_STRING_BUFFER];
 
	UChar *p = draw_buffer;
 

	
 
	*setx_offsets.Append() = p;
 

	
 
	char *loc = str;
 
	for (;;) {
 
		WChar c;
 
		/* We cannot use Utf8Consume as we need the location of the SETX(Y) */
 
		size_t len = Utf8Decode(&c, loc);
 
		*p++ = c;
 

	
 
		if (c == '\0') break;
 
		if (p >= lastof(draw_buffer) - 3) {
 
			/* Make sure we never overflow (even if copying SCC_SETX(Y)). */
 
			*p = '\0';
 
			break;
 
		}
 
		if (c != SCC_SETX && c != SCC_SETXY) {
 
			loc += len;
 
			continue;
 
		}
 

	
 
		if ((align & SA_MASK) != SA_LEFT) {
 
			DEBUG(grf, 1, "Using SETX and/or SETXY when not aligned to the left. Fixing alignment...");
 
			align = SA_LEFT;
 

	
 
			/* For left alignment and change the left so it will roughly be in the
 
			 * middle. This will never cause the string to be completely centered,
 
			 * but once SETX is used you cannot be sure the actual content of the
 
			 * string is centered, so it doesn't really matter. */
 
			align = SA_LEFT | SA_FORCE;
 
			initial_left = left = max(left, (left + right - GetStringBoundingBox(str).width) / 2);
 
		}
 

	
 
		/* We add the begin of the string, but don't add it twice */
 
		if (loc != str) *setx_offsets.Append() = loc;
 
		if (p != draw_buffer) {
 
			*setx_offsets.Append() = p;
 
			p[-1] = '\0';
 
			*p++ = c;
 
		}
 

	
 
		/* Skip the SCC_SETX(Y) ... */
 
		loc += len;
 
		/* ... skip the x coordinate ... */
 
		loc++;
 
		/* ... and finally the y coordinate if it exists */
 
		if (c == SCC_SETXY) loc++;
 
		/* ... copy the x coordinate ... */
 
		*p++ = *loc++;
 
		/* ... and finally copy the y coordinate if it exists */
 
		if (c == SCC_SETXY) *p++ = *loc++;
 
	}
 

	
 
	/* In case we have a RTL language we swap the alignment. */
 
	if (!(align & SA_FORCE) && _dynlang.text_dir == TD_RTL && align != SA_CENTER) align ^= SA_RIGHT;
 

	
 
	/* Now draw the parts. This is done in the reverse order so we can give the
 
	 * BiDi algorithm the room to replace characters. It also simplifies
 
	 * 'ending' the strings. */
 
	for (char **iter = setx_offsets.End(); iter-- != setx_offsets.Begin(); ) {
 
		char *to_draw = *iter;
 
		WChar c;
 
		size_t len = Utf8Decode(&c, to_draw);
 
	for (UChar **iter = setx_offsets.Begin(); iter != setx_offsets.End(); iter++) {
 
		UChar *to_draw = *iter;
 
		int offset = 0;
 

	
 
		/* Skip the SETX(Y) and set the appropriate offsets. */
 
		if (c == SCC_SETX || c == SCC_SETXY) {
 
			*to_draw = '\0';
 
			to_draw += len;
 
		if (*to_draw == SCC_SETX || *to_draw == SCC_SETXY) {
 
			to_draw++;
 
			offset = *to_draw++;
 
			if (c == SCC_SETXY) top = initial_top + *to_draw++;
 
			if (*to_draw == SCC_SETXY) top = initial_top + *to_draw++;
 

	
 
			_cur_fontsize = _last_fontsize;
 
		}
 

	
 
		HandleBiDiAndArabicShapes(to_draw, last);
 
		int w = GetStringBoundingBox(to_draw).width;
 
		to_draw = HandleBiDiAndArabicShapes(to_draw);
 
		int w = GetStringWidth(to_draw);
 

	
 
		/* right is the right most position to draw on. In this case we want to do
 
		 * calculations with the width of the string. In comparison right can be
 
		 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
 
		 * So most +1/-1 additions are to move from lengthof to 'indices'.
 
		 */
 
@@ -752,16 +804,16 @@ Dimension GetStringBoundingBox(const cha
 
		c = Utf8Consume(&str);
 
		if (c == 0) break;
 
		if (IsPrintable(c)) {
 
			br.width += GetCharacterWidth(size, c);
 
		} else {
 
			switch (c) {
 
				case SCC_SETX: br.width += (byte)*str++; break;
 
				case SCC_SETX: br.width = max((int)*str++, br.width); break;
 
				case SCC_SETXY:
 
					br.width += (byte)*str++;
 
					br.height += (byte)*str++;
 
					br.width  = max((int)*str++, br.width);
 
					br.height = max((int)*str++, br.height);
 
					break;
 
				case SCC_TINYFONT: size = FS_SMALL; break;
 
				case SCC_BIGFONT:  size = FS_LARGE; break;
 
				case '\n':
 
					br.height += GetCharacterHeight(size);
 
					if (br.width > max_width) max_width = br.width;
 
@@ -803,18 +855,18 @@ void DrawCharCentered(WChar c, int x, in
 
 *                            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, TextColour colour, bool parse_string_also_when_clipped)
 
static int ReallyDoDrawString(const UChar *string, int x, int y, TextColour &colour, bool parse_string_also_when_clipped)
 
{
 
	DrawPixelInfo *dpi = _cur_dpi;
 
	FontSize size = _cur_fontsize;
 
	WChar c;
 
	int xo = x, yo = y;
 
	UChar c;
 
	int xo = x;
 

	
 
	TextColour previous_colour = colour;
 

	
 
	if (!parse_string_also_when_clipped) {
 
		/* in "mode multiline", the available space have been verified. Not in regular one.
 
		 * So if the string cannot be drawn, return the original start to say so.*/
 
@@ -827,19 +879,19 @@ switch_colour:;
 
	}
 

	
 
check_bounds:
 
	if (y + 19 <= dpi->top || dpi->top + dpi->height <= y) {
 
skip_char:;
 
		for (;;) {
 
			c = Utf8Consume(&string);
 
			c = *string++;
 
			if (!IsPrintable(c)) goto skip_cont;
 
		}
 
	}
 

	
 
	for (;;) {
 
		c = Utf8Consume(&string);
 
		c = *string++;
 
skip_cont:;
 
		if (c == 0) {
 
			_last_fontsize = size;
 
			return x;  // Nothing more to draw, get out. And here is the new x position
 
		}
 
		if (IsPrintable(c)) {
 
@@ -856,17 +908,15 @@ skip_cont:;
 
			previous_colour = colour;
 
			colour = (TextColour)(c - SCC_BLUE);
 
			goto switch_colour;
 
		} else if (c == SCC_PREVIOUS_COLOUR) { // revert to the previous colour
 
			Swap(colour, previous_colour);
 
			goto switch_colour;
 
		} else if (c == SCC_SETX) { // {SETX}
 
			x = xo + (byte)*string++;
 
		} else if (c == SCC_SETXY) {// {SETXY}
 
			x = xo + (byte)*string++;
 
			y = yo + (byte)*string++;
 
		} else if (c == SCC_SETX || c == SCC_SETXY) { // {SETX}/{SETXY}
 
			/* The characters are handled before calling this. */
 
			NOT_REACHED();
 
		} else if (c == SCC_TINYFONT) { // {TINYFONT}
 
			size = FS_SMALL;
 
		} else if (c == SCC_BIGFONT) { // {BIGFONT}
 
			size = FS_LARGE;
 
		} else {
 
			DEBUG(misc, 0, "[utf8] unknown string command character %d", c);
0 comments (0 inline, 0 general)