Changeset - r20457:4996405dfe57
[Not reviewed]
master
0 1 0
rubidium - 11 years ago 2013-06-25 20:44:54
rubidium@openttd.org
(svn r25472) -Cleanup: remove the old methods for drawing text
1 file changed with 0 insertions and 537 deletions:
0 comments (0 inline, 0 general)
src/gfx.cpp
Show inline comments
 
@@ -363,128 +363,6 @@ static void SetColourRemap(TextColour co
 
	_colour_remap_ptr = _string_colourremap;
 
}
 

	
 
#if !defined(WITH_ICU)
 
static WChar *HandleBiDiAndArabicShapes(WChar *text) { return text; }
 
#else
 
#include <unicode/ubidi.h>
 
#include <unicode/ushape.h>
 
#include <unicode/ustring.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
 
 * @return the buffer to draw from
 
 */
 
static WChar *HandleBiDiAndArabicShapes(WChar *buffer)
 
{
 
	UChar input[DRAW_STRING_BUFFER];
 
	UChar intermediate[DRAW_STRING_BUFFER];
 
	static WChar output[DRAW_STRING_BUFFER];
 

	
 
	/* Transform from UTF-32 to internal ICU format of UTF-16. */
 
	UErrorCode err = U_ZERO_ERROR;
 
	int32_t length = 0;
 
	u_strFromUTF32(input, lengthof(input), &length, (UChar32 *)buffer, -1, &err);
 
	if (U_FAILURE(err)) return buffer;
 

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

	
 
	ubidi_setPara(para, input, length, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, NULL, &err);
 
	length = ubidi_writeReordered(para, intermediate, lengthof(intermediate), UBIDI_REMOVE_BIDI_CONTROLS, &err);
 
	length = u_shapeArabic(intermediate, length, input, lengthof(input), U_SHAPE_TEXT_DIRECTION_VISUAL_LTR | U_SHAPE_LETTERS_SHAPE, &err);
 
	ubidi_close(para);
 
	if (U_FAILURE(err)) return buffer;
 

	
 
	/* Transform back to UTF-32. */
 
	u_strToUTF32((UChar32 *)output, lengthof(output), NULL, input, length, &err);
 
	if (U_FAILURE(err)) return buffer;
 

	
 
	/* u_strToUTF32 doesn't add a NUL charcter if the buffer is too small, be safe. */
 
	output[lengthof(output) - 1] = '\0';
 
	return output;
 
}
 
#endif /* WITH_ICU */
 

	
 

	
 
/**
 
 * Truncate a given string to a maximum width if necessary.
 
 * If the string is truncated, add three dots ('...') to show this.
 
 * @param *str string that is checked and possibly truncated
 
 * @param maxw maximum width in pixels of the string
 
 * @param start_fontsize Fontsize to start the text with
 
 * @return new width of (truncated) string
 
 */
 
static int TruncateString(char *str, int maxw, FontSize start_fontsize)
 
{
 
	int w = 0;
 
	FontSize size = start_fontsize;
 
	int ddd, ddd_w;
 

	
 
	WChar c;
 
	char *ddd_pos;
 

	
 
	ddd_w = ddd = GetCharacterWidth(size, '.') * 3;
 

	
 
	for (ddd_pos = str; (c = Utf8Consume(const_cast<const char **>(&str))) != '\0'; ) {
 
		if (IsPrintable(c) && !IsTextDirectionChar(c)) {
 
			w += GetCharacterWidth(size, c);
 

	
 
			if (w > maxw) {
 
				/* string got too big... insert dotdotdot, but make sure we do not
 
				 * print anything beyond the string termination character. */
 
				for (int i = 0; *ddd_pos != '\0' && i < 3; i++, ddd_pos++) *ddd_pos = '.';
 
				*ddd_pos = '\0';
 
				return ddd_w;
 
			}
 
		} else {
 
			if (c == SCC_TINYFONT) {
 
				size = FS_SMALL;
 
				ddd = GetCharacterWidth(size, '.') * 3;
 
			} else if (c == SCC_BIGFONT) {
 
				size = FS_LARGE;
 
				ddd = GetCharacterWidth(size, '.') * 3;
 
			} else if (c == '\n') {
 
				DEBUG(misc, 0, "Drawing string using newlines with DrawString instead of DrawStringMultiLine. Please notify the developers of this: [%s]", str);
 
			}
 
		}
 

	
 
		/* Remember the last position where three dots fit. */
 
		if (w + ddd < maxw) {
 
			ddd_w = w + ddd;
 
			ddd_pos = str;
 
		}
 
	}
 

	
 
	return w;
 
}
 

	
 
static int ReallyDoDrawString(const WChar *string, int x, int y, DrawStringParams &params, bool parse_string_also_when_clipped = false);
 

	
 
/**
 
 * Drawing routine for drawing a laid out line of text.
 
 * @param line      String to draw.
 
@@ -633,109 +511,6 @@ static int DrawLayoutLine(ParagraphLayou
 
}
 

	
 
/**
 
 * Get the real width of the string.
 
 * @param str the string to draw
 
 * @param start_fontsize Fontsize to start the text with
 
 * @return the width.
 
 */
 
static int GetStringWidth(const WChar *str, FontSize start_fontsize)
 
{
 
	FontSize size = start_fontsize;
 
	int max_width;
 
	int width;
 
	WChar c;
 

	
 
	width = max_width = 0;
 
	for (;;) {
 
		c = *str++;
 
		if (c == 0) break;
 
		if (IsPrintable(c) && !IsTextDirectionChar(c)) {
 
			width += GetCharacterWidth(size, c);
 
		} else {
 
			switch (c) {
 
				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.
 
 * @param str    String to draw.
 
 * @param last   The end of the string buffer to draw.
 
 * @param params Text drawing parameters.
 
 * @param align  The alignment of the string when drawing left-to-right. In the
 
 *               case a right-to-left language is chosen this is inverted so it
 
 *               will be drawn in the right direction.
 
 * @param underline Whether to underline what has been drawn or not.
 
 * @param truncate  Whether to truncate the string or not.
 
 *
 
 * @return In case of left or center alignment the right most pixel we have drawn to.
 
 *         In case of right alignment the left most pixel we have drawn to.
 
 */
 
static int DrawString(int left, int right, int top, char *str, const char *last, DrawStringParams &params, StringAlignment align, bool underline = false, bool truncate = true)
 
{
 
	if (truncate) TruncateString(str, right - left + 1, params.fontsize);
 

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

	
 
	const char *text = str;
 
	for (WChar c = Utf8Consume(&text); c != '\0'; c = Utf8Consume(&text)) {
 
		*p++ = c;
 
	}
 
	*p++ = '\0';
 

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

	
 
	WChar *to_draw = HandleBiDiAndArabicShapes(draw_buffer);
 
	int w = GetStringWidth(to_draw, params.fontsize);
 

	
 
	/* 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'.
 
	 */
 
	switch (align & SA_HOR_MASK) {
 
		case SA_LEFT:
 
			/* right + 1 = left + w */
 
			right = left + w - 1;
 
			break;
 

	
 
		case SA_HOR_CENTER:
 
			left  = RoundDivSU(right + 1 + left - w, 2);
 
			/* right + 1 = left + w */
 
			right = left + w - 1;
 
			break;
 

	
 
		case SA_RIGHT:
 
			left = right + 1 - w;
 
			break;
 

	
 
		default:
 
			NOT_REACHED();
 
	}
 

	
 
	ReallyDoDrawString(to_draw, left, top, params, !truncate);
 
	if (underline) {
 
		GfxFillRect(left, top + FONT_HEIGHT_NORMAL, right, top + FONT_HEIGHT_NORMAL, _string_colourremap[1]);
 
	}
 

	
 
	return (align & SA_HOR_MASK) == SA_RIGHT ? left : right;
 
}
 

	
 
/**
 
 * Draw string, possibly truncated to make it fit in its allocated space
 
 *
 
 * @param left   The left most position to draw on.
 
@@ -779,141 +554,6 @@ int DrawString(int left, int right, int 
 
}
 

	
 
/**
 
 * 'Correct' a string to a maximum length. Longer strings will be cut into
 
 * additional lines at whitespace characters if possible. The string parameter
 
 * is modified with terminating characters mid-string which are the
 
 * placeholders for the newlines.
 
 * The string WILL be truncated if there was no whitespace for the current
 
 * line's maximum width.
 
 *
 
 * @note To know if the terminating '\0' is the string end or just a
 
 * newline, the returned 'num' value should be consulted. The num'th '\0',
 
 * starting with index 0 is the real string end.
 
 *
 
 * @param str string to check and correct for length restrictions
 
 * @param last the last valid location (for '\0') in the buffer of str
 
 * @param maxw the maximum width the string can have on one line
 
 * @param size Fontsize to start the text with
 
 * @return return a 32bit wide number consisting of 2 packed values:
 
 *  0 - 15 the number of lines ADDED to the string
 
 * 16 - 31 the fontsize in which the length calculation was done at
 
 */
 
static uint32 FormatStringLinebreaks(char *str, const char *last, int maxw, FontSize size = FS_NORMAL)
 
{
 
	int num = 0;
 

	
 
	assert(maxw > 0);
 

	
 
	for (;;) {
 
		/* The character *after* the last space. */
 
		char *last_space = NULL;
 
		int w = 0;
 

	
 
		for (;;) {
 
			WChar c = Utf8Consume(const_cast<const char **>(&str));
 
			/* whitespace is where we will insert the line-break */
 
			if (IsWhitespace(c)) last_space = str;
 

	
 
			if (IsPrintable(c) && !IsTextDirectionChar(c)) {
 
				int char_w = GetCharacterWidth(size, c);
 
				w += char_w;
 
				if (w > maxw) {
 
					/* The string is longer than maximum width so we need to decide
 
					 * what to do with it. */
 
					if (w == char_w) {
 
						/* The character is wider than allowed width; don't know
 
						 * what to do with this case... bail out! */
 
						return num + (size << 16);
 
					}
 
					if (last_space == NULL) {
 
						/* No space has been found. Just terminate at our current
 
						 * location. This usually happens for languages that do not
 
						 * require spaces in strings, like Chinese, Japanese and
 
						 * Korean. For other languages terminating mid-word might
 
						 * not be the best, but terminating the whole string instead
 
						 * of continuing the word at the next line is worse. */
 
						str = Utf8PrevChar(str);
 
						size_t len = strlen(str);
 
						char *terminator = str + len;
 

	
 
						/* The string location + length of the string + 1 for '\0'
 
						 * always fits; otherwise there's no trailing '\0' and it
 
						 * it not a valid string. */
 
						assert(terminator <= last);
 
						assert(*terminator == '\0');
 

	
 
						/* If the string is too long we have to terminate it earlier. */
 
						if (terminator == last) {
 
							/* Get the 'begin' of the previous character and make that
 
							 * the terminator of the string; we truncate it 'early'. */
 
							*Utf8PrevChar(terminator) = '\0';
 
							len = strlen(str);
 
						}
 
						/* Also move the terminator! */
 
						memmove(str + 1, str, len + 1);
 
						*str = '\0';
 
						/* str needs to point to the character *after* the last space */
 
						str++;
 
					} else {
 
						/* A space is found; perfect place to terminate */
 
						str = last_space;
 
					}
 
					break;
 
				}
 
			} else {
 
				switch (c) {
 
					case '\0': return num + (size << 16);
 
					case SCC_TINYFONT: size = FS_SMALL; break;
 
					case SCC_BIGFONT:  size = FS_LARGE; break;
 
					case '\n': goto end_of_inner_loop;
 
				}
 
			}
 
		}
 
end_of_inner_loop:
 
		/* String didn't fit on line (or a '\n' was encountered), so 'dummy' terminate
 
		 * and increase linecount. We use Utf8PrevChar() as also non 1 char long
 
		 * whitespace separators are supported */
 
		num++;
 
		char *s = Utf8PrevChar(str);
 
		*s++ = '\0';
 

	
 
		/* In which case (see above) we will shift remainder to left and close the gap */
 
		if (str - s >= 1) {
 
			for (; str[-1] != '\0';) *s++ = *str++;
 
		}
 
	}
 
}
 

	
 

	
 
/**
 
 * Calculates height of string (in pixels). Accepts multiline string with '\0' as separators.
 
 * @param src string to check
 
 * @param num number of extra lines (output of FormatStringLinebreaks())
 
 * @param start_fontsize Fontsize to start the text with
 
 * @note assumes text won't be truncated. FormatStringLinebreaks() is a good way to ensure that.
 
 * @return height of pixels of string when it is drawn
 
 */
 
static int GetMultilineStringHeight(const char *src, int num, FontSize start_fontsize)
 
{
 
	int maxy = 0;
 
	int y = 0;
 
	int fh = GetCharacterHeight(start_fontsize);
 

	
 
	for (;;) {
 
		WChar c = Utf8Consume(&src);
 

	
 
		switch (c) {
 
			case 0:            y += fh; if (--num < 0) return maxy; break;
 
			case '\n':         y += fh;                             break;
 
			case SCC_TINYFONT: fh = GetCharacterHeight(FS_SMALL);   break;
 
			case SCC_BIGFONT:  fh = GetCharacterHeight(FS_LARGE);   break;
 
			default:           maxy = max<int>(maxy, y + fh);       break;
 
		}
 
	}
 
}
 

	
 

	
 
/**
 
 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
 
 * @param str string to check
 
 * @param maxw maximum string width
 
@@ -985,103 +625,6 @@ Dimension GetStringMultiLineBoundingBox(
 
 * @param top    The top most position to draw on.
 
 * @param bottom The bottom most position to draw on.
 
 * @param str    String to draw.
 
 * @param last   The end of the string buffer to draw.
 
 * @param colour Colour used for drawing the string, see DoDrawString() for details
 
 * @param align  The horizontal and vertical alignment of the string.
 
 * @param underline Whether to underline all strings
 
 * @param fontsize The size of the initial characters.
 
 *
 
 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
 
 */
 
static int DrawStringMultiLine(int left, int right, int top, int bottom, char *str, const char *last, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
 
{
 
	int maxw = right - left + 1;
 
	int maxh = bottom - top + 1;
 

	
 
	/* It makes no sense to even try if it can't be drawn anyway, or
 
	 * do we really want to support fonts of 0 or less pixels high? */
 
	if (maxh <= 0) return top;
 

	
 
	uint32 tmp = FormatStringLinebreaks(str, last, maxw);
 
	int num = GB(tmp, 0, 16) + 1;
 

	
 
	int mt = GetCharacterHeight((FontSize)GB(tmp, 16, 16));
 
	int total_height = num * mt;
 

	
 
	int skip_lines = 0;
 
	if (total_height > maxh) {
 
		if (maxh < mt) return top; //  Not enough room for a single line.
 
		if ((align & SA_VERT_MASK) == SA_BOTTOM) {
 
			skip_lines = num;
 
			num = maxh / mt;
 
			skip_lines -= num;
 
		} else {
 
			num = maxh / mt;
 
		}
 
		total_height = num * mt;
 
	}
 

	
 
	int y;
 
	switch (align & SA_VERT_MASK) {
 
		case SA_TOP:
 
			y = top;
 
			break;
 

	
 
		case SA_VERT_CENTER:
 
			y = RoundDivSU(bottom + top - total_height, 2);
 
			break;
 

	
 
		case SA_BOTTOM:
 
			y = bottom - total_height;
 
			break;
 

	
 
		default: NOT_REACHED();
 
	}
 

	
 
	const char *src = str;
 
	DrawStringParams params(colour, fontsize);
 
	int written_top = bottom; // Uppermost position of rendering a line of text
 
	for (;;) {
 
		if (skip_lines == 0) {
 
			char buf2[DRAW_STRING_BUFFER];
 
			strecpy(buf2, src, lastof(buf2));
 
			DrawString(left, right, y, buf2, lastof(buf2), params, align, underline, false);
 
			if (written_top > y) written_top = y;
 
			y += mt;
 
			num--;
 
		}
 

	
 
		for (;;) {
 
			WChar c = Utf8Consume(&src);
 
			if (c == 0) {
 
				break;
 
			} else if (skip_lines > 0) {
 
				/* Skipped drawing, so do additional processing to update params. */
 
				if (c >= SCC_BLUE && c <= SCC_BLACK) {
 
					params.SetColour((TextColour)(c - SCC_BLUE));
 
				} else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
 
					params.SetPreviousColour();
 
				} else if (c == SCC_TINYFONT) {
 
					params.SetFontSize(FS_SMALL);
 
				} else if (c == SCC_BIGFONT) {
 
					params.SetFontSize(FS_LARGE);
 
				}
 

	
 
			}
 
		}
 
		if (skip_lines > 0) skip_lines--;
 
		if (num == 0) return ((align & SA_VERT_MASK) == SA_BOTTOM) ? written_top : y;
 
	}
 
}
 

	
 
/**
 
 * Draw string, possibly over multiple lines.
 
 *
 
 * @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.
 
 * @param bottom The bottom most position to draw on.
 
 * @param str    String to draw.
 
 * @param colour Colour used for drawing the string, see DoDrawString() for details
 
 * @param align  The horizontal and vertical alignment of the string.
 
 * @param underline Whether to underline all strings
 
@@ -1202,86 +745,6 @@ 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 positioning.
 
 * @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 params              Text drawing parameters
 
 * @param parse_string_also_when_clipped
 
 *                            By default, always test the available space where to draw the string.
 
 *                            When in multiline 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 WChar *string, int x, int y, DrawStringParams &params, bool parse_string_also_when_clipped)
 
{
 
	DrawPixelInfo *dpi = _cur_dpi;
 
	bool draw_shadow = GetDrawGlyphShadow(FS_NORMAL);
 
	WChar c;
 
	int xo = x;
 

	
 
	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.*/
 
		if (x >= dpi->left + dpi->width || y >= dpi->top + dpi->height) return x;
 
	}
 

	
 
switch_colour:;
 
	SetColourRemap(params.cur_colour);
 

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

	
 
	for (;;) {
 
		c = *string++;
 
skip_cont:;
 
		if (c == 0) {
 
			return x;  // Nothing more to draw, get out. And here is the new x position
 
		}
 
		if (IsPrintable(c) && !IsTextDirectionChar(c)) {
 
			if (x >= dpi->left + dpi->width) goto skip_char;
 
			if (x + _max_char_width >= dpi->left) {
 
				const Sprite *glyph = GetGlyph(params.fontsize, c);
 
				if (draw_shadow && params.fontsize == FS_NORMAL && params.cur_colour != TC_BLACK && !(c >= SCC_SPRITE_START && c <= SCC_SPRITE_END)) {
 
					SetColourRemap(TC_BLACK);
 
					GfxMainBlitter(glyph, x + 1, y + 1, BM_COLOUR_REMAP);
 
					SetColourRemap(params.cur_colour);
 
				}
 
				GfxMainBlitter(glyph, x, y, BM_COLOUR_REMAP);
 
			}
 
			x += GetCharacterWidth(params.fontsize, c);
 
		} else if (c == '\n') { // newline = {}
 
			x = xo;  // We require a new line, so the x coordinate is reset
 
			y += GetCharacterHeight(params.fontsize);
 
			goto check_bounds;
 
		} else if (c >= SCC_BLUE && c <= SCC_BLACK) { // change colour?
 
			params.SetColour((TextColour)(c - SCC_BLUE));
 
			goto switch_colour;
 
		} else if (c == SCC_PREVIOUS_COLOUR) { // revert to the previous colour
 
			params.SetPreviousColour();
 
			goto switch_colour;
 
		} else if (c == SCC_TINYFONT) { // {TINYFONT}
 
			params.SetFontSize(FS_SMALL);
 
		} else if (c == SCC_BIGFONT) { // {BIGFONT}
 
			params.SetFontSize(FS_LARGE);
 
		} else if (!IsTextDirectionChar(c)) {
 
			DEBUG(misc, 0, "[utf8] unknown string command character %d", c);
 
		}
 
	}
 
}
 

	
 
/**
 
 * Get the size of a sprite.
 
 * @param sprid Sprite to examine.
 
 * @param [out] offset Optionally returns the sprite position offset.
0 comments (0 inline, 0 general)