Changeset - r20952:c84424569e57
[Not reviewed]
master
0 2 0
rubidium - 11 years ago 2013-11-16 20:57:54
rubidium@openttd.org
(svn r26017) -Change: allow the fallback and ICU layouter to exist in unison
-Fix [FS#5711]: crash when the ICU layouter thinks a font is corrupted
2 files changed with 234 insertions and 121 deletions:
0 comments (0 inline, 0 general)
src/gfx_layout.cpp
Show inline comments
 
@@ -10,12 +10,13 @@
 
/** @file gfx_layout.cpp Handling of laying out text. */
 

	
 
#include "stdafx.h"
 
#include "gfx_layout.h"
 
#include "string_func.h"
 
#include "strings_func.h"
 
#include "debug.h"
 

	
 
#include "table/control_codes.h"
 

	
 
#ifdef WITH_ICU
 
#include <unicode/ustring.h>
 
#endif /* WITH_ICU */
 
@@ -107,22 +108,78 @@ void Font::getGlyphAdvance(LEGlyphID gly
 

	
 
le_bool Font::getGlyphPoint(LEGlyphID glyph, le_int32 pointNumber, LEPoint &point) const
 
{
 
	return FALSE;
 
}
 

	
 
size_t Layouter::AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c)
 
static size_t AppendToBuffer(UChar *buff, const UChar *buffer_last, WChar c)
 
{
 
	/* Transform from UTF-32 to internal ICU format of UTF-16. */
 
	int32 length = 0;
 
	UErrorCode err = U_ZERO_ERROR;
 
	u_strFromUTF32(buff, buffer_last - buff, &length, (UChar32*)&c, 1, &err);
 
	return length;
 
}
 

	
 
ParagraphLayout *Layouter::GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping)
 
/**
 
 * Wrapper for doing layouts with ICU.
 
 */
 
class ICUParagraphLayout : public AutoDeleteSmallVector<ParagraphLayouter::Line *, 4>, public ParagraphLayouter {
 
	ParagraphLayout *p; ///< The actual ICU paragraph layout.
 
public:
 
	/** Helper for GetLayouter, to get the right type. */
 
	typedef UChar CharType;
 
	/** Helper for GetLayouter, to get whether the layouter supports RTL. */
 
	static const bool SUPPORTS_RTL = true;
 

	
 
	/** Visual run contains data about the bit of text with the same font. */
 
	class ICUVisualRun : public ParagraphLayouter::VisualRun {
 
		const ParagraphLayout::VisualRun *vr; ///< The actual ICU vr.
 

	
 
	public:
 
		ICUVisualRun(const ParagraphLayout::VisualRun *vr) : vr(vr) { }
 

	
 
		const Font *getFont() const          { return (const Font*)vr->getFont(); }
 
		int getGlyphCount() const            { return vr->getGlyphCount(); }
 
		const GlyphID *getGlyphs() const     { return vr->getGlyphs(); }
 
		const float *getPositions() const    { return vr->getPositions(); }
 
		int getLeading() const               { return vr->getLeading(); }
 
		const int *getGlyphToCharMap() const { return vr->getGlyphToCharMap(); }
 
	};
 

	
 
	/** A single line worth of VisualRuns. */
 
	class ICULine : public AutoDeleteSmallVector<ICUVisualRun *, 4>, public ParagraphLayouter::Line {
 
		ParagraphLayout::Line *l; ///< The actual ICU line.
 

	
 
	public:
 
		ICULine(ParagraphLayout::Line *l) : l(l)
 
		{
 
			for (int i = 0; i < l->countRuns(); i++) {
 
				*this->Append() = new ICUVisualRun(l->getVisualRun(i));
 
			}
 
		}
 
		~ICULine() { delete l; }
 

	
 
		int getLeading() const { return l->getLeading(); }
 
		int getWidth() const   { return l->getWidth(); }
 
		int countRuns() const  { return l->countRuns(); }
 
		const ParagraphLayouter::VisualRun *getVisualRun(int run) const { return *this->Get(run); }
 
	};
 

	
 
	ICUParagraphLayout(ParagraphLayout *p) : p(p) { }
 
	~ICUParagraphLayout() { delete p; }
 
	void reflow() { p->reflow(); }
 

	
 
	ParagraphLayouter::Line *nextLine(int max_width)
 
	{
 
		ParagraphLayout::Line *l = p->nextLine(max_width);
 
		return l == NULL ? NULL : new ICULine(l);
 
	}
 
};
 

	
 
static ParagraphLayouter *GetParagraphLayout(UChar *buff, UChar *buff_end, FontMap &fontMapping)
 
{
 
	int32 length = buff_end - buff;
 

	
 
	if (length == 0) {
 
		/* ICU's ParagraphLayout cannot handle empty strings, so fake one. */
 
		buff[0] = ' ';
 
@@ -136,18 +193,85 @@ ParagraphLayout *Layouter::GetParagraphL
 
		runs.add(iter->second, iter->first);
 
	}
 

	
 
	LEErrorCode status = LE_NO_ERROR;
 
	/* ParagraphLayout does not copy "buff", so it must stay valid.
 
	 * "runs" is copied according to the ICU source, but the documentation does not specify anything, so this might break somewhen. */
 
	return new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
 
	ParagraphLayout *p = new ParagraphLayout(buff, length, &runs, NULL, NULL, NULL, _current_text_dir == TD_RTL ? UBIDI_DEFAULT_RTL : UBIDI_DEFAULT_LTR, false, status);
 
	if (status != LE_NO_ERROR) {
 
		delete p;
 
		return NULL;
 
	}
 

	
 
	return new ICUParagraphLayout(p);
 
}
 

	
 
#else /* WITH_ICU */
 
#endif /* WITH_ICU */
 

	
 
/*** Paragraph layout ***/
 
/**
 
 * Class handling the splitting of a paragraph of text into lines and
 
 * visual runs.
 
 *
 
 * One constructs this class with the text that needs to be split into
 
 * lines. Then nextLine is called with the maximum width until NULL is
 
 * returned. Each nextLine call creates VisualRuns which contain the
 
 * length of text that are to be drawn with the same font. In other
 
 * words, the result of this class is a list of sub strings with their
 
 * font. The sub strings are then already fully laid out, and only
 
 * need actual drawing.
 
 *
 
 * The positions in a visual run are sequential pairs of X,Y of the
 
 * begin of each of the glyphs plus an extra pair to mark the end.
 
 *
 
 * @note This variant does not handle left-to-right properly. This
 
 *       is supported in the one ParagraphLayout coming from ICU.
 
 */
 
class FallbackParagraphLayout : public ParagraphLayouter {
 
public:
 
	/** Helper for GetLayouter, to get the right type. */
 
	typedef WChar CharType;
 
	/** Helper for GetLayouter, to get whether the layouter supports RTL. */
 
	static const bool SUPPORTS_RTL = false;
 

	
 
	/** Visual run contains data about the bit of text with the same font. */
 
	class FallbackVisualRun : public ParagraphLayouter::VisualRun {
 
		Font *font;       ///< The font used to layout these.
 
		GlyphID *glyphs;  ///< The glyphs we're drawing.
 
		float *positions; ///< The positions of the glyphs.
 
		int *glyph_to_char; ///< The char index of the glyphs.
 
		int glyph_count;  ///< The number of glyphs.
 

	
 
	public:
 
		FallbackVisualRun(Font *font, const WChar *chars, int glyph_count, int x);
 
		~FallbackVisualRun();
 
		const Font *getFont() const;
 
		int getGlyphCount() const;
 
		const GlyphID *getGlyphs() const;
 
		const float *getPositions() const;
 
		int getLeading() const;
 
		const int *getGlyphToCharMap() const;
 
	};
 

	
 
	/** A single line worth of VisualRuns. */
 
	class FallbackLine : public AutoDeleteSmallVector<FallbackVisualRun *, 4>, public ParagraphLayouter::Line {
 
	public:
 
		int getLeading() const;
 
		int getWidth() const;
 
		int countRuns() const;
 
		const ParagraphLayouter::VisualRun *getVisualRun(int run) const;
 
	};
 

	
 
	const WChar *buffer_begin; ///< Begin of the buffer.
 
	const WChar *buffer;       ///< The current location in the buffer.
 
	FontMap &runs;             ///< The fonts we have to use for this paragraph.
 

	
 
	FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs);
 
	void reflow();
 
	const ParagraphLayouter::Line *nextLine(int max_width);
 
};
 

	
 
/**
 
 * Create the visual run.
 
 * @param font       The font to use for this run.
 
 * @param chars      The characters to use for this run.
 
 * @param char_count The number of characters in this run.
 
@@ -258,13 +382,13 @@ int FallbackParagraphLayout::FallbackLin
 

	
 
	/*
 
	 * The last X position of a run contains is the end of that run.
 
	 * Since there is no left-to-right support, taking this value of
 
	 * the last run gives us the end of the line and thus the width.
 
	 */
 
	const FallbackVisualRun *run = this->getVisualRun(this->countRuns() - 1);
 
	const ParagraphLayouter::VisualRun *run = this->getVisualRun(this->countRuns() - 1);
 
	return (int)run->getPositions()[run->getGlyphCount() * 2];
 
}
 

	
 
/**
 
 * Get the number of runs in this line.
 
 * @return The number of runs.
 
@@ -406,30 +530,93 @@ const ParagraphLayouter::Line *FallbackP
 
 * Appand a wide character to the internal buffer.
 
 * @param buff        The buffer to append to.
 
 * @param buffer_last The end of the buffer.
 
 * @param c           The character to add.
 
 * @return The number of buffer spaces that were used.
 
 */
 
size_t Layouter::AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c)
 
static size_t AppendToBuffer(WChar *buff, const WChar *buffer_last, WChar c)
 
{
 
	*buff = c;
 
	return 1;
 
}
 

	
 
/**
 
 * Get the actual ParagraphLayout for the given buffer.
 
 * @param buff The begin of the buffer.
 
 * @param buff_end The location after the last element in the buffer.
 
 * @param fontMapping THe mapping of the fonts.
 
 * @return The ParagraphLayout instance.
 
 */
 
ParagraphLayouter *Layouter::GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping)
 
static FallbackParagraphLayout *GetParagraphLayout(WChar *buff, WChar *buff_end, FontMap &fontMapping)
 
{
 
	return new FallbackParagraphLayout(buff, buff_end - buff, fontMapping);
 
}
 
#endif /* !WITH_ICU */
 

	
 
/**
 
 * Helper for getting a ParagraphLayouter of the given type.
 
 *
 
 * @note In case no ParagraphLayouter could be constructed, line.layout will be NULL.
 
 * @param line The cache item to store our layouter in.
 
 * @param str The string to create a layouter for.
 
 * @param state The state of the font and color.
 
 * @tparam T The type of layouter we want.
 
 */
 
template <typename T>
 
static inline void GetLayouter(Layouter::LineCacheItem &line, const char *str, FontState state)
 
{
 
	if (line.buffer != NULL) free(line.buffer);
 

	
 
	typename T::CharType *buff_begin = MallocT<typename T::CharType>(DRAW_STRING_BUFFER);
 
	const typename T::CharType *buffer_last = buff_begin + DRAW_STRING_BUFFER;
 
	typename T::CharType *buff = buff_begin;
 
	FontMap &fontMapping = line.runs;
 
	Font *f = Layouter::GetFont(state.fontsize, state.cur_colour);
 

	
 
	line.buffer = buff_begin;
 

	
 
	/*
 
	 * Go through the whole string while adding Font instances to the font map
 
	 * whenever the font changes, and convert the wide characters into a format
 
	 * usable by ParagraphLayout.
 
	 */
 
	for (; buff < buffer_last;) {
 
		WChar c = Utf8Consume(const_cast<const char **>(&str));
 
		if (c == '\0' || c == '\n') {
 
			break;
 
		} else if (c >= SCC_BLUE && c <= SCC_BLACK) {
 
			state.SetColour((TextColour)(c - SCC_BLUE));
 
		} else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
 
			state.SetPreviousColour();
 
		} else if (c == SCC_TINYFONT) {
 
			state.SetFontSize(FS_SMALL);
 
		} else if (c == SCC_BIGFONT) {
 
			state.SetFontSize(FS_LARGE);
 
		} else {
 
			/* Filter out text direction characters that shouldn't be drawn, and
 
			 * will not be handled in the fallback non ICU case because they are
 
			 * mostly needed for RTL languages which need more ICU support. */
 
			if (!T::SUPPORTS_RTL && IsTextDirectionChar(c)) continue;
 
			buff += AppendToBuffer(buff, buffer_last, c);
 
			continue;
 
		}
 

	
 
		if (!fontMapping.Contains(buff - buff_begin)) {
 
			fontMapping.Insert(buff - buff_begin, f);
 
		}
 
		f = Layouter::GetFont(state.fontsize, state.cur_colour);
 
	}
 

	
 
	/* Better safe than sorry. */
 
	*buff = '\0';
 

	
 
	if (!fontMapping.Contains(buff - buff_begin)) {
 
		fontMapping.Insert(buff - buff_begin, f);
 
	}
 
	line.layout = GetParagraphLayout(buff_begin, buff, fontMapping);
 
	line.state_after = state;
 
}
 

	
 
/**
 
 * Create a new layouter.
 
 * @param str      The string to create the layout for.
 
 * @param maxw     The maximum width.
 
 * @param colour   The colour of the font.
 
@@ -454,60 +641,25 @@ Layouter::Layouter(const char *str, int 
 
			/* Line is in cache */
 
			str = lineend + 1;
 
			state = line.state_after;
 
			line.layout->reflow();
 
		} else {
 
			/* Line is new, layout it */
 
			const CharType *buffer_last = lastof(line.buffer);
 
			CharType *buff_begin = line.buffer;
 
			CharType *buff = buff_begin;
 
			FontMap &fontMapping = line.runs;
 
			Font *f = GetFont(state.fontsize, state.cur_colour);
 

	
 
			/*
 
			 * Go through the whole string while adding Font instances to the font map
 
			 * whenever the font changes, and convert the wide characters into a format
 
			 * usable by ParagraphLayout.
 
			 */
 
			for (; buff < buffer_last;) {
 
				c = Utf8Consume(const_cast<const char **>(&str));
 
				if (c == '\0' || c == '\n') {
 
					break;
 
				} else if (c >= SCC_BLUE && c <= SCC_BLACK) {
 
					state.SetColour((TextColour)(c - SCC_BLUE));
 
				} else if (c == SCC_PREVIOUS_COLOUR) { // Revert to the previous colour.
 
					state.SetPreviousColour();
 
				} else if (c == SCC_TINYFONT) {
 
					state.SetFontSize(FS_SMALL);
 
				} else if (c == SCC_BIGFONT) {
 
					state.SetFontSize(FS_LARGE);
 
				} else {
 
#ifndef WITH_ICU
 
					/* Filter out text direction characters that shouldn't be drawn, and
 
					 * will not be handled in the fallback non ICU case because they are
 
					 * mostly needed for RTL languages which need more ICU support. */
 
					if (IsTextDirectionChar(c)) continue;
 
#ifdef WITH_ICU
 
			GetLayouter<ICUParagraphLayout>(line, str, state);
 
			if (line.layout == NULL) {
 
				static bool warned = false;
 
				if (!warned) {
 
					DEBUG(misc, 0, "ICU layouter bailed on the font. Falling back to the fallback layouter");
 
					warned = true;
 
				}
 
				GetLayouter<FallbackParagraphLayout>(line, str, state);
 
			}
 
#else
 
			GetLayouter<FallbackParagraphLayout>(line, str, state);
 
#endif
 
					buff += AppendToBuffer(buff, buffer_last, c);
 
					continue;
 
				}
 

	
 
				if (!fontMapping.Contains(buff - buff_begin)) {
 
					fontMapping.Insert(buff - buff_begin, f);
 
				}
 
				f = GetFont(state.fontsize, state.cur_colour);
 
			}
 

	
 
			/* Better safe than sorry. */
 
			*buff = '\0';
 

	
 
			if (!fontMapping.Contains(buff - buff_begin)) {
 
				fontMapping.Insert(buff - buff_begin, f);
 
			}
 
			line.layout = GetParagraphLayout(buff_begin, buff, fontMapping);
 
			line.state_after = state;
 
		}
 

	
 
		/* Copy all lines into a local cache so we can reuse them later on more easily. */
 
		const ParagraphLayouter::Line *l;
 
		while ((l = line.layout->nextLine(maxw)) != NULL) {
 
			*this->Append() = l;
src/gfx_layout.h
Show inline comments
 
@@ -21,17 +21,13 @@
 

	
 
#ifdef WITH_ICU
 
#include "layout/ParagraphLayout.h"
 
#define ICU_FONTINSTANCE : public LEFontInstance
 
#else /* WITH_ICU */
 
#define ICU_FONTINSTANCE
 
#define FallbackParagraphLayout ParagraphLayout
 
#define FallbackVisualRun VisualRun
 
#define FallbackLine Line
 
#endif /* WITH_ICU */
 
#define ParagraphLayouter ParagraphLayout
 

	
 
/**
 
 * Text drawing parameters, which can change while drawing a line, but are kept between multiple parts
 
 * of the same text, e.g. on line breaks.
 
 */
 
struct FontState {
 
@@ -98,90 +94,53 @@ public:
 
#endif /* WITH_ICU */
 
};
 

	
 
/** Mapping from index to font. */
 
typedef SmallMap<int, Font *> FontMap;
 

	
 
#ifndef WITH_ICU
 
/**
 
 * Class handling the splitting of a paragraph of text into lines and
 
 * visual runs.
 
 *
 
 * One constructs this class with the text that needs to be split into
 
 * lines. Then nextLine is called with the maximum width until NULL is
 
 * returned. Each nextLine call creates VisualRuns which contain the
 
 * length of text that are to be drawn with the same font. In other
 
 * words, the result of this class is a list of sub strings with their
 
 * font. The sub strings are then already fully laid out, and only
 
 * need actual drawing.
 
 *
 
 * The positions in a visual run are sequential pairs of X,Y of the
 
 * begin of each of the glyphs plus an extra pair to mark the end.
 
 *
 
 * @note This variant does not handle left-to-right properly. This
 
 *       is supported in the one ParagraphLayout coming from ICU.
 
 * @note Does not conform to function naming style as it provides a
 
 *       fallback for the ICU class.
 
 * Interface to glue fallback and normal layouter into one.
 
 */
 
class FallbackParagraphLayout {
 
class ParagraphLayouter {
 
public:
 
	/** Visual run contains data about the bit of text with the same font. */
 
	class FallbackVisualRun {
 
		Font *font;       ///< The font used to layout these.
 
		GlyphID *glyphs;  ///< The glyphs we're drawing.
 
		float *positions; ///< The positions of the glyphs.
 
		int *glyph_to_char; ///< The char index of the glyphs.
 
		int glyph_count;  ///< The number of glyphs.
 
	virtual ~ParagraphLayouter() {}
 

	
 
	/** Visual run contains data about the bit of text with the same font. */
 
	class VisualRun {
 
	public:
 
		FallbackVisualRun(Font *font, const WChar *chars, int glyph_count, int x);
 
		~FallbackVisualRun();
 
		const Font *getFont() const;
 
		int getGlyphCount() const;
 
		const GlyphID *getGlyphs() const;
 
		const float *getPositions() const;
 
		int getLeading() const;
 
		const int *getGlyphToCharMap() const;
 
		virtual ~VisualRun() {}
 
		virtual const Font *getFont() const = 0;
 
		virtual int getGlyphCount() const = 0;
 
		virtual const GlyphID *getGlyphs() const = 0;
 
		virtual const float *getPositions() const = 0;
 
		virtual int getLeading() const = 0;
 
		virtual const int *getGlyphToCharMap() const = 0;
 
	};
 

	
 
	/** A single line worth of VisualRuns. */
 
	class FallbackLine : public AutoDeleteSmallVector<FallbackVisualRun *, 4> {
 
	class Line {
 
	public:
 
		int getLeading() const;
 
		int getWidth() const;
 
		int countRuns() const;
 
		const FallbackVisualRun *getVisualRun(int run) const;
 
		virtual ~Line() {}
 
		virtual int getLeading() const = 0;
 
		virtual int getWidth() const = 0;
 
		virtual int countRuns() const = 0;
 
		virtual const VisualRun *getVisualRun(int run) const = 0;
 
	};
 

	
 
	const WChar *buffer_begin; ///< Begin of the buffer.
 
	const WChar *buffer;       ///< The current location in the buffer.
 
	FontMap &runs;             ///< The fonts we have to use for this paragraph.
 

	
 
	FallbackParagraphLayout(WChar *buffer, int length, FontMap &runs);
 
	void reflow();
 
	const Line *nextLine(int max_width);
 
	virtual void reflow() = 0;
 
	virtual const Line *nextLine(int max_width) = 0;
 
};
 
#endif /* !WITH_ICU */
 

	
 
/**
 
 * The layouter performs all the layout work.
 
 *
 
 * It also accounts for the memory allocations and frees.
 
 */
 
class Layouter : public AutoDeleteSmallVector<const ParagraphLayouter::Line *, 4> {
 
#ifdef WITH_ICU
 
	typedef UChar CharType; ///< The type of character used within the layouter.
 
#else /* WITH_ICU */
 
	typedef WChar CharType; ///< The type of character used within the layouter.
 
#endif /* WITH_ICU */
 

	
 
	const char *string; ///< Pointer to the original string.
 

	
 
	size_t AppendToBuffer(CharType *buff, const CharType *buffer_last, WChar c);
 
	ParagraphLayouter *GetParagraphLayout(CharType *buff, CharType *buff_end, FontMap &fontMapping);
 

	
 
	/** Key into the linecache */
 
	struct LineCacheKey {
 
		FontState state_before;  ///< Font state at the beginning of the line.
 
		std::string str;         ///< Source string of the line (including colour and font size codes).
 

	
 
		/** Comparison operator for std::map */
 
@@ -190,34 +149,36 @@ class Layouter : public AutoDeleteSmallV
 
			if (this->state_before.fontsize != other.state_before.fontsize) return this->state_before.fontsize < other.state_before.fontsize;
 
			if (this->state_before.cur_colour != other.state_before.cur_colour) return this->state_before.cur_colour < other.state_before.cur_colour;
 
			if (this->state_before.prev_colour != other.state_before.prev_colour) return this->state_before.prev_colour < other.state_before.prev_colour;
 
			return this->str < other.str;
 
		}
 
	};
 
public:
 
	/** Item in the linecache */
 
	struct LineCacheItem {
 
		/* Stuff that cannot be freed until the ParagraphLayout is freed */
 
		CharType buffer[DRAW_STRING_BUFFER]; ///< Accessed by both ICU's and our ParagraphLayout::nextLine.
 
		FontMap runs;                        ///< Accessed by our ParagraphLayout::nextLine.
 
		void *buffer;              ///< Accessed by both ICU's and our ParagraphLayout::nextLine.
 
		FontMap runs;              ///< Accessed by our ParagraphLayout::nextLine.
 

	
 
		FontState state_after;     ///< Font state after the line.
 
		ParagraphLayouter *layout; ///< Layout of the line.
 

	
 
		LineCacheItem() : layout(NULL) {}
 
		~LineCacheItem() { delete layout; }
 
		LineCacheItem() : buffer(NULL), layout(NULL) {}
 
		~LineCacheItem() { delete layout; free(buffer); }
 
	};
 
private:
 
	typedef std::map<LineCacheKey, LineCacheItem> LineCache;
 
	static LineCache *linecache;
 

	
 
	static LineCacheItem &GetCachedParagraphLayout(const char *str, size_t len, const FontState &state);
 

	
 
	typedef SmallMap<TextColour, Font *> FontColourMap;
 
	static FontColourMap fonts[FS_END];
 
public:
 
	static Font *GetFont(FontSize size, TextColour colour);
 

	
 
public:
 
	Layouter(const char *str, int maxw = INT32_MAX, TextColour colour = TC_FROMSTRING, FontSize fontsize = FS_NORMAL);
 
	Dimension GetBounds();
 
	Point GetCharPosition(const char *ch) const;
 
	const char *GetCharAtPosition(int x) const;
 

	
 
	static void ResetFontCache(FontSize size);
0 comments (0 inline, 0 general)