← Back to team overview

widelands-dev team mailing list archive

[Merge] lp:~widelands-dev/widelands/fh1-multitexture into lp:widelands

 

GunChleoc has proposed merging lp:~widelands-dev/widelands/fh1-multitexture into lp:widelands.

Commit message:
The new font renderer now creates a set of textures that will be blitted separately by the new class RenderedText. This avoids issues with texture sizes exceeding the maximum size supported by graphics drivers when MultilineTextareas get arbitrarily long.

- Fixed positioning and scaling for census & statistics.
- Removed size restriction from FullscreenMenuLoadGame::filename_list_string().
- Got rid of BiDi spaghetti code in Table and Listselect

Also, refactored the following classes and functions:
- text_height()
- Tooltips
- Game tips
- TabPanel

Requested reviews:
  GunChleoc (gunchleoc)

For more details, see:
https://code.launchpad.net/~widelands-dev/widelands/fh1-multitexture/+merge/323903

Notes:

1. There are 2 NOCOMs in this branch for testing purposes - need to be removed before merging.

2. Code review: Please pay special attention to memory management.

3. Testers: Pay attention to scrolling and alignment issues and possible wonky background colors. The messages sent by buildings have a row of dead trees in the background - this is for testing background images and will be removed before the merge.

Known issue: A line of text can start with a blank space. This issue is already in trunk and unrelated to this branch.
-- 
Your team Widelands Developers is subscribed to branch lp:~widelands-dev/widelands/fh1-multitexture.
=== modified file 'src/editor/ui_menus/main_menu_map_options.cc'
--- src/editor/ui_menus/main_menu_map_options.cc	2017-05-13 11:25:24 +0000
+++ src/editor/ui_menus/main_menu_map_options.cc	2017-05-21 18:18:51 +0000
@@ -46,9 +46,7 @@
    : UI::Window(&parent, "map_options", 0, 0, 350, parent.get_inner_h() - 80, _("Map Options")),
      padding_(4),
      indent_(10),
-     labelh_(
-        UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
-        4),
+     labelh_(text_height() + 4),
      checkbox_space_(25),
      butw_((get_inner_w() - 3 * padding_) / 2),
      max_w_(get_inner_w() - 2 * padding_),

=== modified file 'src/editor/ui_menus/main_menu_random_map.cc'
--- src/editor/ui_menus/main_menu_random_map.cc	2017-05-13 11:25:24 +0000
+++ src/editor/ui_menus/main_menu_random_map.cc	2017-05-21 18:18:51 +0000
@@ -52,9 +52,7 @@
      // UI elements
      margin_(4),
      box_width_(get_inner_w() - 2 * margin_),
-     label_height_(
-        UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
-        2),
+     label_height_(text_height() + 2),
      box_(this, margin_, margin_, UI::Box::Vertical, 0, 0, margin_),
      // Size
      width_(&box_,

=== modified file 'src/editor/ui_menus/tool_set_terrain_options_menu.cc'
--- src/editor/ui_menus/tool_set_terrain_options_menu.cc	2017-05-13 11:25:24 +0000
+++ src/editor/ui_menus/tool_set_terrain_options_menu.cc	2017-05-21 18:18:51 +0000
@@ -53,8 +53,8 @@
 	// Blit the main terrain image
 	const Image& terrain_texture = terrain_descr.get_texture(0);
 	Texture* texture = new Texture(terrain_texture.width(), terrain_texture.height());
-	texture->blit(Rectf(0, 0, terrain_texture.width(), terrain_texture.height()), terrain_texture,
-	              Rectf(0, 0, terrain_texture.width(), terrain_texture.height()), 1.,
+	texture->blit(Rectf(0.f, 0.f, terrain_texture.width(), terrain_texture.height()), terrain_texture,
+	              Rectf(0.f, 0.f, terrain_texture.width(), terrain_texture.height()), 1.,
 	              BlendMode::UseAlpha);
 	Vector2i pt(1, terrain_texture.height() - kSmallPicSize - 1);
 
@@ -66,7 +66,7 @@
 
 		texture->blit(Rectf(pt.x, pt.y, terrain_type.icon->width(), terrain_type.icon->height()),
 		              *terrain_type.icon,
-		              Rectf(0, 0, terrain_type.icon->width(), terrain_type.icon->height()), 1.,
+		              Rectf(0.f, 0.f, terrain_type.icon->width(), terrain_type.icon->height()), 1.,
 		              BlendMode::UseAlpha);
 		pt.x += kSmallPicSize + 1;
 	}

=== modified file 'src/graphic/CMakeLists.txt'
--- src/graphic/CMakeLists.txt	2017-04-26 17:11:43 +0000
+++ src/graphic/CMakeLists.txt	2017-05-21 18:18:51 +0000
@@ -109,8 +109,6 @@
     surface.h
     texture.cc
     texture.h
-    texture_cache.cc
-    texture_cache.h
   USES_SDL2
   DEPENDS
     base_exceptions
@@ -268,16 +266,9 @@
     font_handler1.cc
     font_handler1.h
   DEPENDS
-    base_exceptions
-    base_geometry
-    base_log
     base_macros
-    graphic
-    graphic_align
     graphic_image_cache
-    graphic_surface
     graphic_text
-    io_filesystem
 )
 
 wl_library(graphic_text_constants

=== modified file 'src/graphic/animation.cc'
--- src/graphic/animation.cc	2017-05-19 06:05:40 +0000
+++ src/graphic/animation.cc	2017-05-21 18:18:51 +0000
@@ -227,7 +227,8 @@
 	const int w = image->width();
 	const int h = image->height();
 	Texture* rv = new Texture(w / scale_, h / scale_);
-	rv->blit(Rectf(0, 0, w / scale_, h / scale_), *image, Rectf(0, 0, w, h), 1., BlendMode::Copy);
+	rv->blit(
+	   Rectf(0.f, 0.f, w / scale_, h / scale_), *image, Rectf(0.f, 0.f, w, h), 1., BlendMode::Copy);
 	return rv;
 }
 

=== modified file 'src/graphic/build_texture_atlas.cc'
--- src/graphic/build_texture_atlas.cc	2017-01-25 18:55:59 +0000
+++ src/graphic/build_texture_atlas.cc	2017-05-21 18:18:51 +0000
@@ -39,10 +39,6 @@
 // threshold, but not background pictures.
 constexpr int kMaxAreaForTextureAtlas = 240 * 240;
 
-// A graphics card must at least support this size for texture for Widelands to
-// run.
-constexpr int kMinimumSizeForTextures = 2048;
-
 // Returns true if 'filename' ends with an image extension.
 bool is_image(const std::string& filename) {
 	return boost::ends_with(filename, ".png") || boost::ends_with(filename, ".jpg");

=== modified file 'src/graphic/font_handler1.cc'
--- src/graphic/font_handler1.cc	2017-03-25 15:32:49 +0000
+++ src/graphic/font_handler1.cc	2017-05-21 18:18:51 +0000
@@ -19,27 +19,12 @@
 
 #include "graphic/font_handler1.h"
 
-#include <functional>
 #include <memory>
 
 #include <boost/lexical_cast.hpp>
-#include <boost/utility.hpp>
 
-#include "base/log.h"
-#include "base/wexception.h"
-#include "graphic/graphic.h"
-#include "graphic/image.h"
-#include "graphic/image_cache.h"
-#include "graphic/rendertarget.h"
-#include "graphic/text/rt_errors.h"
 #include "graphic/text/rt_render.h"
-#include "graphic/text/sdl_ttf_font.h"
-#include "graphic/texture.h"
-#include "graphic/texture_cache.h"
-#include "io/filesystem/filesystem.h"
-
-using namespace std;
-using namespace boost;
+#include "graphic/text/texture_cache.h"
 
 namespace {
 
@@ -49,58 +34,9 @@
 // that we do not need to lay out text every frame. Last benchmarked at r7712,
 // 30 MB was enough to cache texts for many frames (> 1000), while it is
 // quickly overflowing in the map selection menu.
-// This might need reevaluation is the new font handler is used for more stuff.
-const uint32_t RICHTEXT_TEXTURE_CACHE = 30 << 20;  // shifting converts to MB
-
-// An Image implementation that recreates a rich text texture when needed on
-// the fly. It is meant to be saved into the ImageCache.
-class RTImage : public Image {
-public:
-	RTImage(const string& ghash,
-	        TextureCache* texture_cache,
-	        std::function<RT::Renderer*()> get_renderer,
-	        const string& text,
-	        int gwidth)
-	   : hash_(ghash),
-	     text_(text),
-	     width_(gwidth),
-	     get_renderer_(get_renderer),
-	     texture_cache_(texture_cache) {
-	}
-	virtual ~RTImage() {
-	}
-
-	// Implements Image.
-	int width() const override {
-		return texture()->width();
-	}
-	int height() const override {
-		return texture()->height();
-	}
-
-	const BlitData& blit_data() const override {
-		return texture()->blit_data();
-	}
-
-private:
-	Texture* texture() const {
-		Texture* surf = texture_cache_->get(hash_);
-		if (surf != nullptr) {
-			return surf;
-		}
-		return texture_cache_->insert(
-		   hash_, std::unique_ptr<Texture>(get_renderer_()->render(text_, width_)));
-	}
-
-	const string hash_;
-	const string text_;
-	int width_;
-	std::function<RT::Renderer*()> get_renderer_;
-
-	// Nothing owned.
-	TextureCache* const texture_cache_;
-};
-}
+// This might need reevaluation if this font handler is used for more stuff.
+constexpr uint32_t kTextureCacheSize = 30 << 20;  // shifting converts to MB
+}  // namespace
 
 namespace UI {
 
@@ -108,9 +44,28 @@
 // the ImageCache, so repeated calls to render with the same arguments should not
 // be a problem.
 class FontHandler1 : public IFontHandler1 {
+private:
+	// A transient cache for the generated rendered texts
+	class RenderCache : public TransientCache<RenderedText> {
+	public:
+		RenderCache(uint32_t max_size_in_bytes) : TransientCache<RenderedText>(max_size_in_bytes) {
+		}
+
+		std::shared_ptr<const RenderedText> insert(const std::string& hash,
+		                                           std::shared_ptr<const RenderedText> entry) {
+			// For the size calculation, we estimate that the member variables of each RenderedRect
+			// take up ca. 13 * 32 bytes. It's all pointers or combinations of basic data types, so the
+			// size requirement is pretty constant.
+			constexpr uint32_t kRenderedRectSize = 13 * 32;
+			return TransientCache<RenderedText>::insert(
+			   hash, entry, entry->rects.size() * kRenderedRectSize);
+		}
+	};
+
 public:
 	FontHandler1(ImageCache* image_cache, const std::string& locale)
-	   : texture_cache_(new TextureCache(RICHTEXT_TEXTURE_CACHE)),
+	   : texture_cache_(new TextureCache(kTextureCacheSize)),
+	     render_cache_(new RenderCache(kTextureCacheSize)),
 	     fontsets_(),
 	     fontset_(fontsets_.get_fontset(locale)),
 	     rt_renderer_(new RT::Renderer(image_cache, texture_cache_.get(), fontsets_)),
@@ -119,17 +74,16 @@
 	virtual ~FontHandler1() {
 	}
 
-	const Image* render(const string& text, uint16_t w = 0) override {
-		const string hash = boost::lexical_cast<string>(w) + text;
-
-		if (image_cache_->has(hash))
-			return image_cache_->get(hash);
-
-		std::unique_ptr<RTImage> image(
-		   new RTImage(hash, texture_cache_.get(), [this] { return rt_renderer_.get(); }, text, w));
-		image->width();  // force the rich text to get rendered in case there is an exception thrown.
-
-		return image_cache_->insert(hash, std::move(image));
+	// This will render the 'text' with a width restriction of 'w'. If 'w' == 0, no restriction is
+	// applied.
+	std::shared_ptr<const UI::RenderedText> render(const std::string& text,
+	                                               uint16_t w = 0) override {
+		const std::string hash = boost::lexical_cast<std::string>(w) + text;
+		std::shared_ptr<const RenderedText> rendered_text = render_cache_->get(hash);
+		if (rendered_text.get() == nullptr) {
+			rendered_text = render_cache_->insert(hash, std::move(rt_renderer_->render(text, w)));
+		}
+		return rendered_text;
 	}
 
 	UI::FontSet const* fontset() const override {
@@ -138,12 +92,14 @@
 
 	void reinitialize_fontset(const std::string& locale) override {
 		fontset_ = fontsets_.get_fontset(locale);
-		texture_cache_.get()->flush();
+		texture_cache_->flush();
+		render_cache_->flush();
 		rt_renderer_.reset(new RT::Renderer(image_cache_, texture_cache_.get(), fontsets_));
 	}
 
 private:
 	std::unique_ptr<TextureCache> texture_cache_;
+	std::unique_ptr<RenderCache> render_cache_;
 	UI::FontSets fontsets_;       // All fontsets
 	UI::FontSet const* fontset_;  // The currently active FontSet
 	std::unique_ptr<RT::Renderer> rt_renderer_;

=== modified file 'src/graphic/font_handler1.h'
--- src/graphic/font_handler1.h	2017-02-28 20:07:07 +0000
+++ src/graphic/font_handler1.h	2017-05-21 18:18:51 +0000
@@ -22,16 +22,11 @@
 #define WL_GRAPHIC_FONT_HANDLER1_H
 
 #include <memory>
-#include <string>
 
 #include "base/macros.h"
-#include "base/vector.h"
-#include "graphic/align.h"
+#include "graphic/image_cache.h"
 #include "graphic/text/font_set.h"
-
-class FileSystem;
-class Image;
-class ImageCache;
+#include "graphic/text/rendered_text.h"
 
 namespace UI {
 
@@ -44,11 +39,10 @@
 	virtual ~IFontHandler1() {
 	}
 
-	/*
-	 * Renders the given text into an image. The image is cached and therefore
-	 * ownership remains with this class. Will throw on error.
-	 */
-	virtual const Image* render(const std::string& text, uint16_t w = 0) = 0;
+	/// Renders the given text into a set of images. The images are cached in a transient cache,
+	/// so we share the ownership. Will throw on error.
+	virtual std::shared_ptr<const UI::RenderedText> render(const std::string& text,
+	                                                       uint16_t w = 0) = 0;
 
 	/// Returns the font handler's current FontSet
 	virtual UI::FontSet const* fontset() const = 0;
@@ -61,7 +55,7 @@
 	DISALLOW_COPY_AND_ASSIGN(IFontHandler1);
 };
 
-// Create a new FontHandler1.
+/// Create a new FontHandler1.
 IFontHandler1* create_fonthandler(ImageCache* image_cache, const std::string& locale);
 
 extern IFontHandler1* g_fh1;

=== modified file 'src/graphic/graphic.h'
--- src/graphic/graphic.h	2017-01-25 18:55:59 +0000
+++ src/graphic/graphic.h	2017-05-21 18:18:51 +0000
@@ -33,6 +33,10 @@
 class Screen;
 class StreamWrite;
 
+// A graphics card must at least support this size for texture for Widelands to
+// run.
+constexpr int kMinimumSizeForTextures = 2048;
+
 // Will be send whenever the resolution changes.
 struct GraphicResolutionChanged {
 	CAN_BE_SENT_AS_NOTE(NoteId::GraphicResolutionChanged)

=== modified file 'src/graphic/minimap_renderer.cc'
--- src/graphic/minimap_renderer.cc	2017-05-13 11:25:24 +0000
+++ src/graphic/minimap_renderer.cc	2017-05-21 18:18:51 +0000
@@ -237,7 +237,8 @@
 
 	std::unique_ptr<Texture> texture(new Texture(map_w, map_h));
 
-	texture->fill_rect(Rectf(0, 0, texture->width(), texture->height()), RGBAColor(0, 0, 0, 255));
+	texture->fill_rect(
+	   Rectf(0.f, 0.f, texture->width(), texture->height()), RGBAColor(0, 0, 0, 255));
 
 	// Center the view on the middle of the 'view_area'.
 	const bool zoom = layers & MiniMapLayer::Zoom2;

=== modified file 'src/graphic/playercolor.cc'
--- src/graphic/playercolor.cc	2017-05-15 11:36:22 +0000
+++ src/graphic/playercolor.cc	2017-05-21 18:18:51 +0000
@@ -49,8 +49,8 @@
 	const int w = image->width();
 	const int h = image->height();
 	Texture* pc_image = new Texture(w, h);
-	pc_image->fill_rect(Rectf(0, 0, w, h), RGBAColor(0, 0, 0, 0));
-	pc_image->blit_blended(Rectf(0, 0, w, h), *image, *color_mask, Rectf(0, 0, w, h), clr);
+	pc_image->fill_rect(Rectf(0.f, 0.f, w, h), RGBAColor(0, 0, 0, 0));
+	pc_image->blit_blended(Rectf(0.f, 0.f, w, h), *image, *color_mask, Rectf(0.f, 0.f, w, h), clr);
 	g_gr->images().insert(hash, std::unique_ptr<const Texture>(std::move(pc_image)));
 	assert(g_gr->images().has(hash));
 	return g_gr->images().get(hash);

=== modified file 'src/graphic/rendertarget.cc'
--- src/graphic/rendertarget.cc	2017-05-13 18:48:26 +0000
+++ src/graphic/rendertarget.cc	2017-05-21 18:18:51 +0000
@@ -23,6 +23,7 @@
 #include "graphic/animation.h"
 #include "graphic/graphic.h"
 #include "graphic/surface.h"
+#include "graphic/text_layout.h"
 
 /**
  * Build a render target for the given surface.
@@ -142,9 +143,16 @@
  *
  * This blit function copies the pixels to the destination surface.
  */
-void RenderTarget::blit(const Vector2i& dst, const Image* image, BlendMode blend_mode) {
-	Rectf source_rect(Vector2i::zero(), image->width(), image->height());
-	Rectf destination_rect(dst.x, dst.y, source_rect.w, source_rect.h);
+void RenderTarget::blit(const Vector2i& dst,
+                        const Image* image,
+                        BlendMode blend_mode,
+                        UI::Align align) {
+	assert(image != nullptr);
+	Vector2i destination_point(dst);
+	UI::correct_for_align(align, image->width(), &destination_point);
+
+	Rectf source_rect(0, 0, image->width(), image->height());
+	Rectf destination_rect(destination_point.x, destination_point.y, source_rect.w, source_rect.h);
 
 	if (to_surface_geometry(&destination_rect, &source_rect)) {
 		// I seem to remember seeing 1. a lot in blitting calls.
@@ -155,9 +163,13 @@
 
 void RenderTarget::blit_monochrome(const Vector2i& dst,
                                    const Image* image,
-                                   const RGBAColor& blend_mode) {
-	Rectf source_rect(Vector2i::zero(), image->width(), image->height());
-	Rectf destination_rect(dst.x, dst.y, source_rect.w, source_rect.h);
+                                   const RGBAColor& blend_mode,
+                                   UI::Align align) {
+	Vector2i destination_point(dst);
+	UI::correct_for_align(align, image->width(), &destination_point);
+
+	Rectf source_rect(0, 0, image->width(), image->height());
+	Rectf destination_rect(destination_point.x, destination_point.y, source_rect.w, source_rect.h);
 
 	if (to_surface_geometry(&destination_rect, &source_rect)) {
 		surface_->blit_monochrome(destination_rect, *image, source_rect, blend_mode);

=== modified file 'src/graphic/rendertarget.h'
--- src/graphic/rendertarget.h	2017-05-13 18:48:26 +0000
+++ src/graphic/rendertarget.h	2017-05-21 18:18:51 +0000
@@ -62,10 +62,16 @@
 	void fill_rect(const Recti&, const RGBAColor&, BlendMode blend_mode = BlendMode::Copy);
 	void brighten_rect(const Recti&, int32_t factor);
 
-	void blit(const Vector2i& dst, const Image* image, BlendMode blend_mode = BlendMode::UseAlpha);
+	void blit(const Vector2i& dst,
+	          const Image* image,
+	          BlendMode blend_mode = BlendMode::UseAlpha,
+	          UI::Align = UI::Align::kLeft);
 
 	// Like blit. See MonochromeBlitProgram for details.
-	void blit_monochrome(const Vector2i& dst, const Image* image, const RGBAColor& blend_mode);
+	void blit_monochrome(const Vector2i& dst,
+	                     const Image* image,
+	                     const RGBAColor& blend_mode,
+	                     UI::Align = UI::Align::kLeft);
 
 	void blitrect(const Vector2i& dst,
 	              const Image* image,

=== modified file 'src/graphic/text/CMakeLists.txt'
--- src/graphic/text/CMakeLists.txt	2017-03-29 12:36:20 +0000
+++ src/graphic/text/CMakeLists.txt	2017-05-21 18:18:51 +0000
@@ -14,10 +14,14 @@
     rt_parse.h
     rt_render.cc
     rt_render.h
+    rendered_text.cc
+    rendered_text.h
     sdl_ttf_font.cc
     sdl_ttf_font.h
     textstream.cc
     textstream.h
+    texture_cache.h
+    transient_cache.h
   USES_SDL2
   USES_SDL2_TTF
   USES_ICU

=== added file 'src/graphic/text/rendered_text.cc'
--- src/graphic/text/rendered_text.cc	1970-01-01 00:00:00 +0000
+++ src/graphic/text/rendered_text.cc	2017-05-21 18:18:51 +0000
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2017 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#include "graphic/text/rendered_text.h"
+
+#include <memory>
+
+#include "graphic/graphic.h"
+#include "graphic/text_layout.h"
+
+namespace UI {
+// RenderedRect
+RenderedRect::RenderedRect(const Recti& init_rect,
+                           std::shared_ptr<const Image> init_image,
+                           bool visited,
+                           const RGBColor& color,
+                           bool is_background_color_set,
+                           DrawMode init_mode)
+   : rect_(init_rect),
+     transient_image_(init_image),
+     permanent_image_(nullptr),
+     visited_(visited),
+     background_color_(color),
+     is_background_color_set_(is_background_color_set),
+     mode_(init_mode) {
+}
+RenderedRect::RenderedRect(const Recti& init_rect,
+                           const Image* init_image,
+                           bool visited,
+                           const RGBColor& color,
+                           bool is_background_color_set,
+                           DrawMode init_mode)
+   : rect_(init_rect),
+     transient_image_(nullptr),
+     permanent_image_(init_image),
+     visited_(visited),
+     background_color_(color),
+     is_background_color_set_(is_background_color_set),
+     mode_(init_mode) {
+}
+
+RenderedRect::RenderedRect(const Recti& init_rect, const Image* init_image)
+   : RenderedRect(init_rect, init_image, false, RGBColor(0, 0, 0), false, DrawMode::kTile) {
+}
+RenderedRect::RenderedRect(const Recti& init_rect, const RGBColor& color)
+   : RenderedRect(init_rect, nullptr, false, color, true, DrawMode::kTile) {
+}
+RenderedRect::RenderedRect(std::shared_ptr<const Image> init_image)
+   : RenderedRect(Recti(0, 0, init_image->width(), init_image->height()),
+                  init_image,
+                  false,
+                  RGBColor(0, 0, 0),
+                  false,
+                  DrawMode::kBlit) {
+}
+RenderedRect::RenderedRect(const Image* init_image)
+   : RenderedRect(Recti(0, 0, init_image->width(), init_image->height()),
+                  init_image,
+                  false,
+                  RGBColor(0, 0, 0),
+                  false,
+                  DrawMode::kBlit) {
+}
+
+const Image* RenderedRect::image() const {
+	assert(permanent_image_ == nullptr || transient_image_.get() == nullptr);
+	return permanent_image_ == nullptr ? transient_image_.get() : permanent_image_;
+}
+
+int RenderedRect::x() const {
+	return rect_.x;
+}
+
+int RenderedRect::y() const {
+	return rect_.y;
+}
+
+int RenderedRect::width() const {
+	return rect_.w;
+}
+int RenderedRect::height() const {
+	return rect_.h;
+}
+
+void RenderedRect::set_origin(const Vector2i& new_origin) {
+	rect_.x = new_origin.x;
+	rect_.y = new_origin.y;
+}
+void RenderedRect::set_visited() {
+	visited_ = true;
+}
+bool RenderedRect::was_visited() const {
+	return visited_;
+}
+
+bool RenderedRect::has_background_color() const {
+	return is_background_color_set_;
+}
+const RGBColor& RenderedRect::background_color() const {
+	return background_color_;
+}
+
+RenderedRect::DrawMode RenderedRect::mode() const {
+	return mode_;
+}
+
+// RenderedText
+int RenderedText::width() const {
+	int result = 0;
+	for (const auto& rect : rects) {
+		result = std::max(result, rect->x() + rect->width());
+	}
+	return result;
+}
+int RenderedText::height() const {
+	int result = 0;
+	for (const auto& rect : rects) {
+		result = std::max(result, rect->y() + rect->height());
+	}
+	return result;
+}
+
+void RenderedText::draw(RenderTarget& dst,
+                        const Vector2i& position,
+                        const Recti& region,
+                        Align align,
+                        CropMode cropmode) const {
+
+	// Un-const the position and adjust for alignment according to region width
+	Vector2i aligned_pos(position.x, position.y);
+
+	// For cropping images that don't fit
+	int offset_x = 0;
+	if (cropmode == CropMode::kHorizontal) {
+		UI::correct_for_align(align, width(), &aligned_pos);
+		if (align != UI::Align::kLeft) {
+			for (const auto& rect : rects) {
+				offset_x = std::min(region.w - rect->width(), offset_x);
+			}
+			if (align == UI::Align::kCenter) {
+				offset_x /= 2;
+			}
+		}
+	} else {
+		aligned_pos.x -= region.x;
+		aligned_pos.y -= region.y;
+		UI::correct_for_align(align, region.w, &aligned_pos);
+	}
+
+	// Blit the rects
+	for (const auto& rect : rects) {
+		const Vector2i blit_point(aligned_pos.x + rect->x(), aligned_pos.y + rect->y());
+
+		// Draw Solid background Color
+		if (rect->has_background_color()) {
+#ifndef NDEBUG
+			const int maximum_size = kMinimumSizeForTextures;
+#else
+			const int maximum_size = g_gr->max_texture_size();
+#endif
+			// NOCOM Replace 'test' with 'maximum_size' when testing is done.
+			const int test = 4;
+			const int tile_width = std::min(test, rect->width());
+			const int tile_height = std::min(test, rect->height());
+			for (int tile_x = blit_point.x; tile_x + tile_width <= blit_point.x + rect->width();
+			     tile_x += tile_width) {
+				for (int tile_y = blit_point.y; tile_y + tile_height <= blit_point.y + rect->height();
+				     tile_y += tile_height) {
+					dst.fill_rect(
+					   Recti(tile_x, tile_y, tile_width, tile_height), rect->background_color());
+				}
+			}
+		}
+
+		if (rect->image() != nullptr) {
+			switch (rect->mode()) {
+			// Draw a foreground texture
+			case RenderedRect::DrawMode::kBlit: {
+				switch (cropmode) {
+				case CropMode::kRenderTarget:
+					// dst will handle any cropping
+					dst.blit(blit_point, rect->image());
+					break;
+				case CropMode::kHorizontal:
+					blit_cropped(dst, offset_x, position, blit_point, *rect, region, align);
+				}
+			} break;
+			// Draw a background image (tiling)
+			case RenderedRect::DrawMode::kTile:
+				dst.tile(
+				   Recti(blit_point, rect->width(), rect->height()), rect->image(), Vector2i(0, 0));
+				break;
+			}
+		}
+	}
+}
+
+void RenderedText::draw(RenderTarget& dst, const Vector2i& position, UI::Align align) const {
+	draw(dst, position, Recti(0, 0, width(), height()), align);
+}
+
+// Crop horizontally if it doesn't fit
+void RenderedText::blit_cropped(RenderTarget& dst,
+                                int offset_x,
+                                const Vector2i& position,
+                                const Vector2i& blit_point,
+                                const RenderedRect& rect,
+                                const Recti& region,
+                                Align align) const {
+
+	int blit_width = rect.width();
+	int cropped_left = 0;
+	if (align != UI::Align::kLeft) {
+		if (rect.x() + rect.width() + offset_x <= region.x) {
+			// Falls off the left-hand side
+			return;
+		}
+		if (rect.x() + offset_x < 0) {
+			// Needs cropping
+			blit_width = rect.width() + offset_x + rect.x() - region.x;
+			cropped_left = rect.width() - blit_width;
+		}
+	}
+
+	if (align != UI::Align::kRight) {
+		if (rect.x() + rect.width() - offset_x > region.w - region.x) {
+			blit_width = region.w - rect.x() - offset_x;
+		}
+	}
+
+	// Don't blit tiny or negative width
+	if (blit_width < 3) {
+		return;
+	}
+
+	dst.blitrect(
+	   Vector2i(cropped_left > 0 ?
+	               position.x + region.x - (align == UI::Align::kRight ? region.w : region.w / 2) :
+	               blit_point.x,
+	            blit_point.y),
+	   rect.image(), Recti(cropped_left > 0 ? cropped_left : 0, region.y, blit_width, region.h));
+}
+
+}  // namespace UI

=== added file 'src/graphic/text/rendered_text.h'
--- src/graphic/text/rendered_text.h	1970-01-01 00:00:00 +0000
+++ src/graphic/text/rendered_text.h	2017-05-21 18:18:51 +0000
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ */
+
+#ifndef WL_GRAPHIC_TEXT_RENDERED_TEXT_H
+#define WL_GRAPHIC_TEXT_RENDERED_TEXT_H
+
+#include <memory>
+#include <vector>
+
+#include "base/rect.h"
+#include "base/vector.h"
+#include "graphic/image.h"
+#include "graphic/rendertarget.h"
+#include "graphic/texture.h"
+
+namespace UI {
+
+/// A rectangle that contains blitting information for rendered text.
+class RenderedRect {
+public:
+	/// Whether the RenderedRect's image should be blitted once or tiled
+	enum class DrawMode { kBlit, kTile };
+
+private:
+	// The image is managed by a transient cache
+	RenderedRect(const Recti& init_rect,
+	             std::shared_ptr<const Image> init_image,
+	             bool visited,
+	             const RGBColor& color,
+	             bool is_background_color_set,
+	             DrawMode init_mode);
+	// The image is managed by a pernament cache
+	RenderedRect(const Recti& init_rect,
+	             const Image* init_image,
+	             bool visited,
+	             const RGBColor& color,
+	             bool is_background_color_set,
+	             DrawMode init_mode);
+
+public:
+	/// RenderedRect will contain a background image that should be tiled
+	RenderedRect(const Recti& init_rect, const Image* init_image);
+
+	/// RenderedRect will contain a background color that should be tiled
+	RenderedRect(const Recti& init_rect, const RGBColor& color);
+
+	/// RenderedRect will contain a normal image that is managed by a transient cache.
+	/// Use this if the image is managed by an instance of TextureCache.
+	RenderedRect(std::shared_ptr<const Image> init_image);
+
+	/// RenderedRect will contain a normal image that is managed by a permanent cache.
+	/// Use this if the image is managed by g_gr->images().
+	RenderedRect(const Image* init_image);
+	~RenderedRect() {
+	}
+
+	/// An image to be blitted. Can be nullptr.
+	const Image* image() const;
+
+	/// The x position of the rectangle
+	int x() const;
+	/// The y position of the rectangle
+	int y() const;
+	/// The width of the rectangle
+	int width() const;
+	/// The height of the rectangle
+	int height() const;
+
+	/// Change x and y position of the rectangle.
+	void set_origin(const Vector2i& new_origin);
+
+	/// Set that this rectangle was already visited by the font renderer. Needed by the font renderer
+	/// for correct positioning.
+	void set_visited();
+	/// Whether this rectangle was already visited by the font renderer
+	bool was_visited() const;
+
+	/// Whether this rectangle contains a background color
+	bool has_background_color() const;
+	/// This rectangle's background color
+	const RGBColor& background_color() const;
+
+	/// Whether the RenderedRect's image should be blitted once or tiled
+	DrawMode mode() const;
+
+private:
+	Recti rect_;
+	// We have 2 image objects depending on the caching situation - only use one of them at the same
+	// time.
+	std::shared_ptr<const Image> transient_image_;  // Shared ownership, managed by a transient cache
+	const Image* permanent_image_;                  // Not owned, managed by a permanent cache
+	bool visited_;
+	const RGBColor background_color_;
+	const bool is_background_color_set_;
+	const DrawMode mode_;
+};
+
+struct RenderedText {
+	/// RenderedRects that can be drawn on screen
+	std::vector<std::unique_ptr<RenderedRect>> rects;
+
+	/// The width occupied  by all rects in pixels.
+	int width() const;
+	/// The height occupied  by all rects in pixels.
+	int height() const;
+
+	enum class CropMode {
+		kRenderTarget,  // The RenderTarget will handle all cropping
+		kHorizontal     // The draw() method will handle horizontal cropping
+	};
+
+	/// Draw the rects. 'position', 'region' and 'align' are used to control the overall drawing
+	/// position and cropping.
+	/// For 'cropmode', use kRenderTarget if you wish the text to fill the whole RenderTarget, e.g.
+	/// for scrolling panels. Use kHorizontal for horizontal cropping in smaller elements, e.g. table
+	/// cells.
+	void draw(RenderTarget& dst,
+	          const Vector2i& position,
+	          const Recti& region,
+	          UI::Align align = UI::Align::kLeft,
+	          CropMode cropmode = CropMode::kRenderTarget) const;
+
+	/// Draw the rects without cropping. 'position' and 'align' are used to control the overall
+	/// drawing position
+	void draw(RenderTarget& dst, const Vector2i& position, UI::Align align = UI::Align::kLeft) const;
+
+private:
+	/// Helper function for CropMode::kHorizontal
+	void blit_cropped(RenderTarget& dst,
+	                  int offset_x,
+	                  const Vector2i& position,
+	                  const Vector2i& blit_point,
+	                  const RenderedRect& rect,
+	                  const Recti& region,
+	                  Align align) const;
+};
+
+}  // namespace UI
+
+#endif  // end of include guard: WL_GRAPHIC_TEXT_RENDERED_TEXT_H

=== modified file 'src/graphic/text/rt_render.cc'
--- src/graphic/text/rt_render.cc	2017-05-03 07:24:06 +0000
+++ src/graphic/text/rt_render.cc	2017-05-21 18:18:51 +0000
@@ -43,6 +43,7 @@
 #include "graphic/text/bidi.h"
 #include "graphic/text/font_io.h"
 #include "graphic/text/font_set.h"
+#include "graphic/text/rendered_text.h"
 #include "graphic/text/rt_parse.h"
 #include "graphic/text/sdl_ttf_font.h"
 #include "graphic/text/textstream.h"
@@ -52,7 +53,7 @@
 #include "io/filesystem/layered_filesystem.h"
 
 using namespace std;
-
+// TODO(GunChleoc): text line can start with space text node when it's within a div.
 namespace RT {
 
 static const uint16_t INFINITE_WIDTH = 65535;  // 2^16-1
@@ -227,15 +228,18 @@
 	virtual ~RenderNode() {
 	}
 
-	virtual uint16_t width() = 0;
-	virtual uint16_t height() = 0;
-	virtual uint16_t hotspot_y() = 0;
-	virtual Texture* render(TextureCache* texture_cache) = 0;
-
-	virtual bool is_non_mandatory_space() {
+	virtual uint16_t width() const = 0;
+	virtual uint16_t height() const = 0;
+	virtual uint16_t hotspot_y() const = 0;
+	virtual UI::RenderedText* render(TextureCache* texture_cache) = 0;
+
+	// TODO(GunChleoc): Remove this function once conversion is finished and well tested.
+	virtual std::string debug_info() const = 0;
+
+	virtual bool is_non_mandatory_space() const {
 		return false;
 	}
-	virtual bool is_expanding() {
+	virtual bool is_expanding() const {
 		return false;
 	}
 	virtual void set_w(uint16_t) {
@@ -245,19 +249,19 @@
 		return vector<Reference>();
 	}
 
-	Floating get_floating() {
+	Floating get_floating() const {
 		return floating_;
 	}
 	void set_floating(Floating f) {
 		floating_ = f;
 	}
-	UI::Align halign() {
+	UI::Align halign() const {
 		return halign_;
 	}
 	void set_halign(UI::Align ghalign) {
 		halign_ = ghalign;
 	}
-	UI::Align valign() {
+	UI::Align valign() const {
 		return valign_;
 	}
 	void set_valign(UI::Align gvalign) {
@@ -269,13 +273,38 @@
 	void set_y(int32_t ny) {
 		y_ = ny;
 	}
-	int32_t x() {
+	int32_t x() const {
 		return x_;
 	}
-	int32_t y() {
+	int32_t y() const {
 		return y_;
 	}
 
+protected:
+	/// Throws a TextureTooBig exception if the given dimensions would be bigger than the graphics
+	/// can handle
+	void check_size(int check_w, int check_h) {
+// Test for minimum supported size in debug builds.
+#ifndef NDEBUG
+		const int maximum_size = kMinimumSizeForTextures;
+#else
+		const int maximum_size = g_gr->max_texture_size();
+#endif
+		if (check_w > maximum_size || check_h > maximum_size) {
+			const std::string error_message =
+			   (boost::format("Texture (%d, %d) too big! Maximum size is %d.") % check_w % check_h %
+			    maximum_size)
+			      .str();
+			log("%s\n", error_message.c_str());
+			throw TextureTooBig(error_message);
+		}
+	}
+
+	/// Check the size for the node's own dimensions
+	void check_size() {
+		check_size(width(), height());
+	}
+
 private:
 	Floating floating_;
 	UI::Align halign_;
@@ -479,13 +508,17 @@
 	virtual ~TextNode() {
 	}
 
-	uint16_t width() override {
+	std::string debug_info() const override {
+		return "'" + txt_ + "'";
+	}
+
+	uint16_t width() const override {
 		return w_;
 	}
-	uint16_t height() override {
+	uint16_t height() const override {
 		return h_ + nodestyle_.spacing;
 	}
-	uint16_t hotspot_y() override;
+	uint16_t hotspot_y() const override;
 	const vector<Reference> get_references() override {
 		vector<Reference> rv;
 		if (!nodestyle_.reference.empty()) {
@@ -495,7 +528,7 @@
 		return rv;
 	}
 
-	Texture* render(TextureCache* texture_cache) override;
+	UI::RenderedText* render(TextureCache* texture_cache) override;
 
 protected:
 	uint16_t w_, h_;
@@ -512,18 +545,20 @@
      fontcache_(font),
      font_(dynamic_cast<SdlTtfFont&>(fontcache_.get_font(&nodestyle_))) {
 	font_.dimensions(txt_, ns.font_style, &w_, &h_);
+	check_size();
 }
-uint16_t TextNode::hotspot_y() {
+uint16_t TextNode::hotspot_y() const {
 	return font_.ascent(nodestyle_.font_style);
 }
 
-Texture* TextNode::render(TextureCache* texture_cache) {
-	const Texture& img =
+UI::RenderedText* TextNode::render(TextureCache* texture_cache) {
+	auto rendered_image =
 	   font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
-	Texture* rv = new Texture(img.width(), img.height());
-	rv->blit(Rectf(0, 0, img.width(), img.height()), img, Rectf(0, 0, img.width(), img.height()), 1.,
-	         BlendMode::Copy);
-	return rv;
+	assert(rendered_image.get() != nullptr);
+	UI::RenderedText* rendered_text = new UI::RenderedText();
+	rendered_text->rects.push_back(
+	   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
+	return rendered_text;
 }
 
 /*
@@ -536,12 +571,18 @@
 	   FontCache& font, NodeStyle& ns, uint16_t w, const string& txt, bool expanding = false)
 	   : TextNode(font, ns, txt), is_expanding_(expanding) {
 		w_ = w;
+		check_size();
 	}
 	virtual ~FillingTextNode() {
 	}
-	Texture* render(TextureCache*) override;
-
-	bool is_expanding() override {
+
+	std::string debug_info() const override {
+		return "ft";
+	}
+
+	UI::RenderedText* render(TextureCache*) override;
+
+	bool is_expanding() const override {
 		return is_expanding_;
 	}
 	void set_w(uint16_t w) override {
@@ -551,15 +592,29 @@
 private:
 	bool is_expanding_;
 };
-Texture* FillingTextNode::render(TextureCache* texture_cache) {
-	const Texture& t =
-	   font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
-	Texture* rv = new Texture(w_, h_);
-	for (uint16_t curx = 0; curx < w_; curx += t.width()) {
-		Rectf srcrect(0.f, 0.f, min<int>(t.width(), w_ - curx), h_);
-		rv->blit(Rectf(curx, 0, srcrect.w, srcrect.h), t, srcrect, 1., BlendMode::Copy);
+UI::RenderedText* FillingTextNode::render(TextureCache* texture_cache) {
+	UI::RenderedText* rendered_text = new UI::RenderedText();
+	const std::string hash =
+	   (boost::format("rt:fill:%s:%s:%i:%i:%i:%s") % txt_ % nodestyle_.font_color.hex_value() %
+	    nodestyle_.font_style % width() % height() % (is_expanding_ ? "e" : "f"))
+	      .str();
+
+	std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
+	if (rendered_image.get() == nullptr) {
+		std::shared_ptr<const Image> ttf =
+		   font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
+		auto texture = std::make_shared<Texture>(width(), height());
+		for (uint16_t curx = 0; curx < w_; curx += ttf->width()) {
+			Rectf srcrect(0.f, 0.f, min<int>(ttf->width(), w_ - curx), h_);
+			texture->blit(
+			   Rectf(curx, 0, srcrect.w, srcrect.h), *ttf.get(), srcrect, 1., BlendMode::Copy);
+		}
+		rendered_image = texture_cache->insert(hash, std::move(texture));
 	}
-	return rv;
+	assert(rendered_image.get() != nullptr);
+	rendered_text->rects.push_back(
+	   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
+	return rendered_text;
 }
 
 /*
@@ -569,20 +624,34 @@
 class WordSpacerNode : public TextNode {
 public:
 	WordSpacerNode(FontCache& font, NodeStyle& ns) : TextNode(font, ns, " ") {
+		check_size();
 	}
 	static void show_spaces(bool t) {
 		show_spaces_ = t;
 	}
 
-	Texture* render(TextureCache* texture_cache) override {
+	std::string debug_info() const override {
+		return "wsp";
+	}
+
+	UI::RenderedText* render(TextureCache* texture_cache) override {
 		if (show_spaces_) {
-			Texture* rv = new Texture(w_, h_);
-			rv->fill_rect(Rectf(0, 0, w_, h_), RGBAColor(0xcc, 0, 0, 0xcc));
-			return rv;
+			UI::RenderedText* rendered_text = new UI::RenderedText();
+			const std::string hash = (boost::format("rt:wsp:%i:%i") % width() % height()).str();
+			std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
+			if (rendered_image.get() == nullptr) {
+				auto texture = std::make_shared<Texture>(width(), height());
+				texture->fill_rect(Rectf(0.f, 0.f, w_, h_), RGBAColor(0xcc, 0, 0, 0xcc));
+				rendered_image = texture_cache->insert(hash, std::move(texture));
+			}
+			assert(rendered_image.get() != nullptr);
+			rendered_text->rects.push_back(
+			   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
+			return rendered_text;
 		}
 		return TextNode::render(texture_cache);
 	}
-	bool is_non_mandatory_space() override {
+	bool is_non_mandatory_space() const override {
 		return true;
 	}
 
@@ -599,19 +668,24 @@
 public:
 	NewlineNode(NodeStyle& ns) : RenderNode(ns) {
 	}
-	uint16_t height() override {
+
+	std::string debug_info() const override {
+		return "nl";
+	}
+
+	uint16_t height() const override {
 		return 0;
 	}
-	uint16_t width() override {
+	uint16_t width() const override {
 		return INFINITE_WIDTH;
 	}
-	uint16_t hotspot_y() override {
+	uint16_t hotspot_y() const override {
 		return 0;
 	}
-	Texture* render(TextureCache* /* texture_cache */) override {
+	UI::RenderedText* render(TextureCache* /* texture_cache */) override {
 		NEVER_HERE();
 	}
-	bool is_non_mandatory_space() override {
+	bool is_non_mandatory_space() const override {
 		return true;
 	}
 };
@@ -622,52 +696,76 @@
 class SpaceNode : public RenderNode {
 public:
 	SpaceNode(NodeStyle& ns, uint16_t w, uint16_t h = 0, bool expanding = false)
-	   : RenderNode(ns), w_(w), h_(h), background_image_(nullptr), is_expanding_(expanding) {
-	}
-
-	uint16_t height() override {
+	   : RenderNode(ns),
+	     w_(w),
+	     h_(h),
+	     background_image_(nullptr),
+	     filename_(""),
+	     is_expanding_(expanding) {
+		check_size();
+	}
+
+	std::string debug_info() const override {
+		return "sp";
+	}
+
+	uint16_t height() const override {
 		return h_;
 	}
-	uint16_t width() override {
+	uint16_t width() const override {
 		return w_;
 	}
-	uint16_t hotspot_y() override {
+	uint16_t hotspot_y() const override {
 		return h_;
 	}
-	Texture* render(TextureCache* /* texture_cache */) override {
-		Texture* rv = new Texture(w_, h_);
+	UI::RenderedText* render(TextureCache* texture_cache) override {
+		UI::RenderedText* rendered_text = new UI::RenderedText();
+		const std::string hash = (boost::format("rt:sp:%s:%i:%i:%s") % filename_ % width() %
+		                          height() % (is_expanding_ ? "e" : "f"))
+		                            .str();
 
-		// Draw background image (tiling)
-		if (background_image_) {
-			Rectf dst;
-			Rectf srcrect(0, 0, 1, 1);
-			for (uint16_t curx = 0; curx < w_; curx += background_image_->width()) {
-				dst.x = curx;
-				dst.y = 0;
-				srcrect.w = dst.w = min<int>(background_image_->width(), w_ - curx);
-				srcrect.h = dst.h = h_;
-				rv->blit(dst, *background_image_, srcrect, 1., BlendMode::Copy);
+		std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
+		if (rendered_image.get() == nullptr) {
+			// Draw background image (tiling)
+			auto texture = std::make_shared<Texture>(width(), height());
+			if (background_image_ != nullptr) {
+				Rectf dst;
+				Rectf srcrect(0, 0, 1, 1);
+				for (uint16_t curx = 0; curx < w_; curx += background_image_->width()) {
+					dst.x = curx;
+					dst.y = 0;
+					srcrect.w = dst.w = min<int>(background_image_->width(), w_ - curx);
+					srcrect.h = dst.h = h_;
+					texture->blit(dst, *background_image_, srcrect, 1., BlendMode::Copy);
+				}
+			} else {
+				texture->fill_rect(Rectf(0.f, 0.f, w_, h_), RGBAColor(255, 255, 255, 0));
 			}
-		} else {
-			rv->fill_rect(Rectf(0, 0, w_, h_), RGBAColor(255, 255, 255, 0));
+			rendered_image = texture_cache->insert(hash, std::move(texture));
 		}
-		return rv;
+		assert(rendered_image.get() != nullptr);
+		rendered_text->rects.push_back(
+		   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
+		return rendered_text;
 	}
-	bool is_expanding() override {
+
+	bool is_expanding() const override {
 		return is_expanding_;
 	}
 	void set_w(uint16_t w) override {
 		w_ = w;
 	}
 
-	void set_background(const Image* s) {
+	void set_background(const Image* s, const std::string& filename) {
 		background_image_ = s;
+		filename_ = filename;
 		h_ = s->height();
 	}
 
 private:
 	uint16_t w_, h_;
 	const Image* background_image_;  // not owned
+	std::string filename_;
 	bool is_expanding_;
 };
 
@@ -690,13 +788,17 @@
 		nodes_to_render_.clear();
 	}
 
-	uint16_t width() override {
+	std::string debug_info() const override {
+		return "div";
+	}
+
+	uint16_t width() const override {
 		return w_ + margin_.left + margin_.right;
 	}
-	uint16_t height() override {
+	uint16_t height() const override {
 		return h_ + margin_.top + margin_.bottom;
 	}
-	uint16_t hotspot_y() override {
+	uint16_t hotspot_y() const override {
 		return height();
 	}
 
@@ -704,60 +806,48 @@
 		return desired_width_;
 	}
 
-	Texture* render(TextureCache* texture_cache) override {
-		if (width() > g_gr->max_texture_size() || height() > g_gr->max_texture_size()) {
-			const std::string error_message =
-			   (boost::format("Texture (%d, %d) too big! Maximum size is %d.") % width() % height() %
-			    g_gr->max_texture_size())
-			      .str();
-			log("%s\n", error_message.c_str());
-			throw TextureTooBig(error_message);
-		}
-		Texture* rv = new Texture(width(), height());
-		rv->fill_rect(Rectf(0, 0, rv->width(), rv->height()), RGBAColor(255, 255, 255, 0));
+	UI::RenderedText* render(TextureCache* texture_cache) override {
+		UI::RenderedText* rendered_text = new UI::RenderedText();
+		// Preserve padding
+		rendered_text->rects.push_back(std::unique_ptr<UI::RenderedRect>(
+		   new UI::RenderedRect(Recti(0, 0, width(), height()), nullptr)));
 
 		// Draw Solid background Color
-		bool set_alpha = true;
 		if (is_background_color_set_) {
-			rv->fill_rect(Rectf(margin_.left, margin_.top, w_, h_), background_color_);
-			set_alpha = false;
+			UI::RenderedRect* bg_rect =
+			   new UI::RenderedRect(Recti(margin_.left, margin_.top, w_, h_), background_color_);
+			// Size is automatically adjusted in RenderedText while blitting, so no need to call
+			// check_size() here.
+			rendered_text->rects.push_back(std::unique_ptr<UI::RenderedRect>(std::move(bg_rect)));
 		}
 
 		// Draw background image (tiling)
-		if (background_image_) {
-			Rectf dst;
-			Rectf src(0, 0, 0, 0);
-
-			for (uint16_t cury = margin_.top; cury < h_ + margin_.top;
-			     cury += background_image_->height()) {
-				for (uint16_t curx = margin_.left; curx < w_ + margin_.left;
-				     curx += background_image_->width()) {
-					dst.x = curx;
-					dst.y = cury;
-					src.w = dst.w = min<int>(background_image_->width(), w_ + margin_.left - curx);
-					src.h = dst.h = min<int>(background_image_->height(), h_ + margin_.top - cury);
-					rv->blit(dst, *background_image_, src, 1., BlendMode::Copy);
+		if (background_image_ != nullptr) {
+			UI::RenderedRect* bg_rect =
+			   new UI::RenderedRect(Recti(margin_.left, margin_.top, w_, h_), background_image_);
+			check_size(bg_rect->width(), bg_rect->height());
+			rendered_text->rects.push_back(std::unique_ptr<UI::RenderedRect>(std::move(bg_rect)));
+		}
+
+		for (RenderNode* n : nodes_to_render_) {
+			const auto& renderme = n->render(texture_cache);
+			for (auto& rendered_rect : renderme->rects) {
+				if (rendered_rect->was_visited()) {
+					rendered_rect->set_origin(
+					   Vector2i(x() + rendered_rect->x(), y() + rendered_rect->y() + margin_.top));
+
+				} else {
+					rendered_rect->set_origin(
+					   Vector2i(x() + n->x() + margin_.left, y() + n->y() + margin_.top));
+					rendered_rect->set_visited();
 				}
-			}
-			set_alpha = false;
-		}
-
-		for (RenderNode* n : nodes_to_render_) {
-			Texture* node_texture = n->render(texture_cache);
-			if (node_texture) {
-				Rectf dst(n->x() + margin_.left, n->y() + margin_.top, node_texture->width(),
-				          node_texture->height());
-				Rectf src(0, 0, node_texture->width(), node_texture->height());
-				rv->blit(
-				   dst, *node_texture, src, 1., set_alpha ? BlendMode::Copy : BlendMode::UseAlpha);
-				delete node_texture;
+				rendered_text->rects.push_back(std::move(rendered_rect));
 			}
 			delete n;
 		}
-
 		nodes_to_render_.clear();
 
-		return rv;
+		return rendered_text;
 	}
 	const vector<Reference> get_references() override {
 		return refs_;
@@ -806,30 +896,62 @@
 	   : RenderNode(ns),
 	     image_(use_playercolor ? playercolor_image(color, image_filename) :
 	                              g_gr->images().get(image_filename)),
-	     scale_(scale) {
-	}
-
-	uint16_t width() override {
+	     filename_(image_filename),
+	     scale_(scale),
+	     color_(color),
+	     use_playercolor_(use_playercolor) {
+		check_size();
+	}
+
+	std::string debug_info() const override {
+		return "img";
+	}
+
+	uint16_t width() const override {
 		return scale_ * image_->width();
 	}
-	uint16_t height() override {
-		return scale_ * image_->height();
-	}
-	uint16_t hotspot_y() override {
-		return scale_ * image_->height();
-	}
-	Texture* render(TextureCache* texture_cache) override;
+	uint16_t height() const override {
+		return scale_ * image_->height();
+	}
+	uint16_t hotspot_y() const override {
+		return scale_ * image_->height();
+	}
+	UI::RenderedText* render(TextureCache* texture_cache) override;
 
 private:
 	const Image* image_;
+	const std::string filename_;
 	const double scale_;
+	const RGBColor& color_;
+	bool use_playercolor_;
 };
 
-Texture* ImgRenderNode::render(TextureCache* /* texture_cache */) {
-	Texture* rv = new Texture(width(), height());
-	rv->blit(Rectf(0, 0, width(), height()), *image_, Rectf(0, 0, image_->width(), image_->height()),
-	         1., BlendMode::Copy);
-	return rv;
+UI::RenderedText* ImgRenderNode::render(TextureCache* texture_cache) {
+	UI::RenderedText* rendered_text = new UI::RenderedText();
+
+	if (scale_ == 1.0) {
+		// Image can be used as is, and has already been cached in g_gr->images()
+		assert(image_ != nullptr);
+		rendered_text->rects.push_back(
+		   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(image_)));
+	} else {
+		const std::string hash = (boost::format("rt:img:%s:%s:%i:%i") % filename_ %
+		                          (use_playercolor_ ? color_.hex_value() : "") % width() % height())
+		                            .str();
+		std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
+		if (rendered_image.get() == nullptr) {
+			auto texture = std::make_shared<Texture>(width(), height());
+			texture->blit(Rectf(0.f, 0.f, width(), height()), *image_,
+			              Rectf(0.f, 0.f, image_->width(), image_->height()), 1., BlendMode::Copy);
+			rendered_image = texture_cache->insert(hash, std::move(texture));
+		}
+
+		assert(rendered_image.get() != nullptr);
+		rendered_text->rects.push_back(
+		   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
+	}
+
+	return rendered_text;
 }
 // End: Helper Stuff
 
@@ -1136,6 +1258,7 @@
 	                 const UI::FontSets& fontsets)
 	   : TagHandler(tag, fc, ns, image_cache, init_renderer_style, fontsets),
 	     background_image_(nullptr),
+	     image_filename_(""),
 	     space_(0) {
 	}
 
@@ -1151,6 +1274,7 @@
 			fill_text_ = a["fill"].get_string();
 			try {
 				background_image_ = image_cache_->get(fill_text_);
+				image_filename_ = fill_text_;
 				fill_text_ = "";
 			} catch (ImageNotFound&) {
 			}
@@ -1172,7 +1296,7 @@
 				sn = new SpaceNode(nodestyle_, 0, 0, true);
 
 			if (background_image_)
-				sn->set_background(background_image_);
+				sn->set_background(background_image_, image_filename_);
 			rn = sn;
 		}
 		nodes.push_back(rn);
@@ -1181,6 +1305,7 @@
 private:
 	string fill_text_;
 	const Image* background_image_;
+	std::string image_filename_;
 	uint16_t space_;
 };
 
@@ -1481,10 +1606,11 @@
 	return nodes[0];
 }
 
-Texture* Renderer::render(const string& text, uint16_t width, const TagSet& allowed_tags) {
+std::shared_ptr<const UI::RenderedText>
+Renderer::render(const string& text, uint16_t width, const TagSet& allowed_tags) {
 	std::unique_ptr<RenderNode> node(layout_(text, width, allowed_tags));
 
-	return node->render(texture_cache_);
+	return std::shared_ptr<const UI::RenderedText>(std::move(node->render(texture_cache_)));
 }
 
 IRefMap*

=== modified file 'src/graphic/text/rt_render.h'
--- src/graphic/text/rt_render.h	2017-01-25 18:55:59 +0000
+++ src/graphic/text/rt_render.h	2017-05-21 18:18:51 +0000
@@ -28,11 +28,10 @@
 
 #include "graphic/color.h"
 #include "graphic/image.h"
+#include "graphic/image_cache.h"
 #include "graphic/text/font_set.h"
-
-class Texture;
-class ImageCache;
-class TextureCache;
+#include "graphic/text/rendered_text.h"
+#include "graphic/text/texture_cache.h"
 
 namespace RT {
 
@@ -80,9 +79,9 @@
 	~Renderer();
 
 	// Render the given string in the given width. Restricts the allowed tags to
-	// the ones in TagSet. The renderer does not do caching in the TextureCache
-	// for its individual nodes, but the font render does.
-	Texture* render(const std::string&, uint16_t width, const TagSet& tagset = TagSet());
+	// the ones in TagSet.
+	std::shared_ptr<const UI::RenderedText>
+	render(const std::string&, uint16_t width, const TagSet& tagset = TagSet());
 
 	// Returns a reference map of the clickable hyperlinks in the image. This
 	// will do no caching and needs to do all layouting, so do not call this too

=== modified file 'src/graphic/text/sdl_ttf_font.cc'
--- src/graphic/text/sdl_ttf_font.cc	2017-01-25 18:55:59 +0000
+++ src/graphic/text/sdl_ttf_font.cc	2017-05-21 18:18:51 +0000
@@ -63,17 +63,18 @@
 	*gh = h;
 }
 
-const Texture& SdlTtfFont::render(const std::string& txt,
-                                  const RGBColor& clr,
-                                  int style,
-                                  TextureCache* texture_cache) {
+std::shared_ptr<const Image> SdlTtfFont::render(const std::string& txt,
+                                                const RGBColor& clr,
+                                                int style,
+                                                TextureCache* texture_cache) {
 	const std::string hash =
-	   (boost::format("%s:%s:%i:%02x%02x%02x:%i") % font_name_ % ptsize_ % txt %
+	   (boost::format("ttf:%s:%s:%i:%02x%02x%02x:%i") % font_name_ % ptsize_ % txt %
 	    static_cast<int>(clr.r) % static_cast<int>(clr.g) % static_cast<int>(clr.b) % style)
 	      .str();
-	const Texture* rv = texture_cache->get(hash);
-	if (rv)
-		return *rv;
+	std::shared_ptr<const Image> rv = texture_cache->get(hash);
+	if (rv.get() != nullptr) {
+		return rv;
+	}
 
 	set_style(style);
 
@@ -128,7 +129,7 @@
 		throw RenderError(
 		   (boost::format("Rendering '%s' gave the error: %s") % txt % TTF_GetError()).str());
 
-	return *texture_cache->insert(hash, std::unique_ptr<Texture>(new Texture(text_surface)));
+	return texture_cache->insert(hash, std::make_shared<Texture>(text_surface));
 }
 
 uint16_t SdlTtfFont::ascent(int style) const {

=== modified file 'src/graphic/text/sdl_ttf_font.h'
--- src/graphic/text/sdl_ttf_font.h	2017-02-28 20:07:07 +0000
+++ src/graphic/text/sdl_ttf_font.h	2017-05-21 18:18:51 +0000
@@ -25,8 +25,8 @@
 
 #include <SDL_ttf.h>
 
+#include "graphic/text/texture_cache.h"
 #include "graphic/texture.h"
-#include "graphic/texture_cache.h"
 
 namespace RT {
 
@@ -51,7 +51,8 @@
 	}
 
 	virtual void dimensions(const std::string&, int, uint16_t*, uint16_t*) = 0;
-	virtual const Texture& render(const std::string&, const RGBColor& clr, int, TextureCache*) = 0;
+	virtual std::shared_ptr<const Image>
+	render(const std::string&, const RGBColor& clr, int, TextureCache*) = 0;
 
 	virtual uint16_t ascent(int) const = 0;
 	virtual TTF_Font* get_ttf_font() const = 0;
@@ -64,7 +65,8 @@
 	virtual ~SdlTtfFont();
 
 	void dimensions(const std::string&, int, uint16_t* w, uint16_t* h) override;
-	const Texture& render(const std::string&, const RGBColor& clr, int, TextureCache*) override;
+	std::shared_ptr<const Image>
+	render(const std::string&, const RGBColor& clr, int, TextureCache*) override;
 	uint16_t ascent(int) const override;
 	TTF_Font* get_ttf_font() const override {
 		return font_;

=== modified file 'src/graphic/text/test/CMakeLists.txt'
--- src/graphic/text/test/CMakeLists.txt	2015-10-15 10:46:37 +0000
+++ src/graphic/text/test/CMakeLists.txt	2017-05-21 18:18:51 +0000
@@ -23,7 +23,6 @@
   DEPENDS
     base_i18n
     graphic_image_cache
-    graphic_surface
     graphic_text
     io_filesystem
 )

=== modified file 'src/graphic/text/test/render.cc'
--- src/graphic/text/test/render.cc	2017-01-25 18:55:59 +0000
+++ src/graphic/text/test/render.cc	2017-05-21 18:18:51 +0000
@@ -28,7 +28,7 @@
 #include "graphic/image_cache.h"
 #include "graphic/text/rt_render.h"
 #include "graphic/text/test/paths.h"
-#include "graphic/texture_cache.h"
+#include "graphic/text/texture_cache.h"
 #include "io/filesystem/layered_filesystem.h"
 
 StandaloneRenderer::StandaloneRenderer() {

=== modified file 'src/graphic/text/test/render_richtext.cc'
--- src/graphic/text/test/render_richtext.cc	2017-01-25 18:55:59 +0000
+++ src/graphic/text/test/render_richtext.cc	2017-05-21 18:18:51 +0000
@@ -33,6 +33,7 @@
 #include "config.h"
 #include "graphic/graphic.h"
 #include "graphic/image_io.h"
+#include "graphic/rendertarget.h"
 #include "graphic/text/rt_errors.h"
 #include "graphic/text/test/render.h"
 #include "graphic/texture.h"
@@ -138,11 +139,15 @@
 	StandaloneRenderer standalone_renderer;
 
 	try {
-		std::unique_ptr<Texture> texture(
-		   standalone_renderer.renderer()->render(txt, w, allowed_tags));
+		std::shared_ptr<const UI::RenderedText> rendered_text =
+		   standalone_renderer.renderer()->render(txt, w, allowed_tags);
+		std::unique_ptr<Texture> texture(new Texture(rendered_text->width(), rendered_text->height()));
+		std::unique_ptr<RenderTarget> dst(new RenderTarget(texture.get()));
+		rendered_text->draw(*dst.get(), Vector2i::zero());
 
 		std::unique_ptr<FileSystem> fs(&FileSystem::create("."));
 		std::unique_ptr<StreamWrite> sw(fs->open_stream_write(outname));
+
 		if (!save_to_png(texture.get(), sw.get(), ColorType::RGBA)) {
 			std::cout << "Could not encode PNG." << std::endl;
 		}

=== added file 'src/graphic/text/texture_cache.h'
--- src/graphic/text/texture_cache.h	1970-01-01 00:00:00 +0000
+++ src/graphic/text/texture_cache.h	2017-05-21 18:18:51 +0000
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 by the Widelands Development Team
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef WL_GRAPHIC_TEXT_TEXTURE_CACHE_H
+#define WL_GRAPHIC_TEXT_TEXTURE_CACHE_H
+
+#include <memory>
+
+#include "graphic/image.h"
+#include "graphic/text/transient_cache.h"
+
+class TextureCache : public TransientCache<Image> {
+public:
+	TextureCache(uint32_t max_size_in_bytes) : TransientCache<Image>(max_size_in_bytes) {
+	}
+
+	std::shared_ptr<const Image> insert(const std::string& hash,
+	                                    std::shared_ptr<const Image> entry) {
+		return TransientCache<Image>::insert(hash, entry, entry->width() * entry->height() * 4);
+	}
+};
+
+#endif  // end of include guard: WL_GRAPHIC_TEXT_TEXTURE_CACHE_H

=== renamed file 'src/graphic/texture_cache.h' => 'src/graphic/text/transient_cache.h'
--- src/graphic/texture_cache.h	2017-01-25 18:55:59 +0000
+++ src/graphic/text/transient_cache.h	2017-05-21 18:18:51 +0000
@@ -17,8 +17,8 @@
  *
  */
 
-#ifndef WL_GRAPHIC_TEXTURE_CACHE_H
-#define WL_GRAPHIC_TEXTURE_CACHE_H
+#ifndef WL_GRAPHIC_TEXT_TRANSIENT_CACHE_H
+#define WL_GRAPHIC_TEXT_TRANSIENT_CACHE_H
 
 #include <cassert>
 #include <list>
@@ -26,45 +26,47 @@
 #include <memory>
 #include <string>
 
+#include <SDL.h>
 #include <boost/utility.hpp>
 
+#include "base/log.h"
 #include "base/macros.h"
 
-class Texture;
+// The implementation took inspiration from
+// https://timday.bitbucket.io/lru.html, but our use case here is a little
+// different.
 
-// Caches transient Surfaces, i.e. those that are always free to be deleted
-// because they can be regenerated - somebody else must then recreate them when
-// they are needed again.
-//
-// Nothing in Widelands should hold onto a Surface they get from this class,
-// instead, they should use it only temporarily and rerequest it whenever they
-// need it.
-class TextureCache {
+/// Caches transient rendered text. The entries will be kept until the memory limit is reached,
+/// then the stalest entries will be deleted to make room for new entries.
+///
+/// We use shared_ptr so that other objects can hold onto the textures if they need them more
+/// permanently.
+template <typename T> class TransientCache {
 public:
-	// Create a new Cache whichs combined pixels data in all transient surfaces
-	// are always below the 'max_size_in_bytes'.
-	TextureCache(uint32_t max_size_in_bytes);
-	~TextureCache();
+	/// Create a new cache in whith the combined data for all transient entries is always below the
+	/// 'max_size_in_bytes'.
+	TransientCache(uint32_t max_size_in_bytes);
+	~TransientCache();
 
-	/// Deletes all surfaces in the cache leaving it as if it were just created.
+	/// Deletes all entries in the cache, leaving it as if it were just created.
 	void flush();
 
 	/// Returns an entry if it is cached, nullptr otherwise.
-	Texture* get(const std::string& hash);
+	std::shared_ptr<const T> get(const std::string& hash);
 
-	// Inserts this entry into the TextureCache. asserts() that there is no
-	// entry with this hash already cached. Returns the given Surface for
-	// convenience. If 'transient' is false, this surface will not be deleted
-	// automatically - use this if surfaces are around for a long time and
-	// recreation is expensive (i.e. images loaded from disk).
-	Texture* insert(const std::string& hash, std::unique_ptr<Texture> texture);
+	/// Inserts this entry of type T into the cache. asserts() that there is no entry with this hash
+	/// already cached. Returns the given T for convenience.
+	std::shared_ptr<const T>
+	insert(const std::string& hash, std::shared_ptr<const T> entry, uint32_t entry_size);
 
 private:
+	/// Drop the oldest entry
 	void drop();
 
 	using AccessHistory = std::list<std::string>;
 	struct Entry {
-		std::unique_ptr<Texture> texture;
+		std::shared_ptr<const T> entry;
+		uint32_t size;
 		uint32_t last_access;  // Mainly for debugging and analysis.
 		const AccessHistory::iterator list_iterator;
 	};
@@ -74,7 +76,70 @@
 	std::map<std::string, Entry> entries_;
 	AccessHistory access_history_;
 
-	DISALLOW_COPY_AND_ASSIGN(TextureCache);
+	DISALLOW_COPY_AND_ASSIGN(TransientCache);
 };
 
-#endif  // end of include guard: WL_GRAPHIC_TEXTURE_CACHE_H
+// Implementation
+
+template <typename T>
+TransientCache<T>::TransientCache(uint32_t max_size_in_bytes)
+   : max_size_in_bytes_(max_size_in_bytes), size_in_bytes_(0) {
+}
+template <typename T> TransientCache<T>::~TransientCache() {
+	flush();
+}
+
+template <typename T> void TransientCache<T>::flush() {
+	access_history_.clear();
+	size_in_bytes_ = 0;
+	entries_.clear();
+}
+
+/// Returns an entry if it is cached, nullptr otherwise.
+template <typename T> std::shared_ptr<const T> TransientCache<T>::get(const std::string& hash) {
+	const auto it = entries_.find(hash);
+	if (it == entries_.end()) {
+		return std::shared_ptr<const T>(nullptr);
+	}
+
+	// Move this to the back of the access list to signal that we have used this
+	// recently and update last access time.
+	access_history_.splice(access_history_.end(), access_history_, it->second.list_iterator);
+	it->second.last_access = SDL_GetTicks();
+	return it->second.entry;
+}
+
+template <typename T>
+std::shared_ptr<const T> TransientCache<T>::insert(const std::string& hash,
+                                                   std::shared_ptr<const T> entry,
+                                                   uint32_t entry_size) {
+	assert(entries_.find(hash) == entries_.end());
+
+	while (size_in_bytes_ + entry_size > max_size_in_bytes_) {
+		drop();
+	}
+
+	// Record hash as most-recently-used.
+	AccessHistory::iterator it = access_history_.insert(access_history_.end(), hash);
+	size_in_bytes_ += entry_size;
+	return entries_.insert(make_pair(hash, Entry{std::move(entry), entry_size, SDL_GetTicks(), it}))
+	   .first->second.entry;
+}
+
+template <typename T> void TransientCache<T>::drop() {
+	assert(!access_history_.empty());
+
+	// Identify least recently used key
+	const auto it = entries_.find(access_history_.front());
+	assert(it != entries_.end());
+
+	size_in_bytes_ -= it->second.size;
+	log("TransientCache: Dropping %d bytes, new size %d. Hash: %s\n", it->second.size,
+	    size_in_bytes_, it->first.c_str());
+
+	// Erase both elements to completely purge record
+	entries_.erase(it);
+	access_history_.pop_front();
+}
+
+#endif  // end of include guard: WL_GRAPHIC_TEXT_TRANSIENT_CACHE_H

=== modified file 'src/graphic/text_layout.cc'
--- src/graphic/text_layout.cc	2017-05-13 13:14:29 +0000
+++ src/graphic/text_layout.cc	2017-05-21 18:18:51 +0000
@@ -48,14 +48,15 @@
 	boost::replace_all(*text, "&amp;", "&");  // Must be performed last
 }
 
-uint32_t text_width(const std::string& text, int ptsize) {
+int text_width(const std::string& text, int ptsize) {
 	return UI::g_fh1->render(as_editorfont(text, ptsize - UI::g_fh1->fontset()->size_offset()))
 	   ->width();
 }
 
-uint32_t text_height(const std::string& text, int ptsize) {
-	return UI::g_fh1->render(as_editorfont(text.empty() ? "." : text,
-	                                       ptsize - UI::g_fh1->fontset()->size_offset()))
+int text_height(int ptsize, UI::FontSet::Face face) {
+	return UI::g_fh1
+	   ->render(as_aligned(UI::g_fh1->fontset()->representative_character(), UI::Align::kLeft,
+	                       ptsize - UI::g_fh1->fontset()->size_offset(), RGBColor(0, 0, 0), face))
 	   ->height();
 }
 
@@ -162,8 +163,10 @@
 	           .str());
 }
 
-const Image* autofit_ui_text(const std::string& text, int width, RGBColor color, int fontsize) {
-	const Image* result = UI::g_fh1->render(as_uifont(richtext_escape(text), fontsize, color));
+std::shared_ptr<const UI::RenderedText>
+autofit_ui_text(const std::string& text, int width, RGBColor color, int fontsize) {
+	std::shared_ptr<const UI::RenderedText> result =
+	   UI::g_fh1->render(as_uifont(richtext_escape(text), fontsize, color));
 	if (width > 0) {  // Autofit
 		for (; result->width() > width && fontsize >= kMinimumFontSize; --fontsize) {
 			result = UI::g_fh1->render(
@@ -179,9 +182,14 @@
  * This mirrors the horizontal alignment for RTL languages.
  *
  * Do not store this value as it is based on the global font setting.
+ *
+ * If 'checkme' is not empty, mirror the alignment if the first 20 characters contain an RTL
+ * character. Otherwise, mirror if the current fontset is RTL.
  */
-Align mirror_alignment(Align alignment) {
-	if (UI::g_fh1->fontset()->is_rtl()) {
+Align mirror_alignment(Align alignment, const std::string& checkme) {
+	bool do_swap_alignment = checkme.empty() ? UI::g_fh1->fontset()->is_rtl() :
+	                                           i18n::has_rtl_character(checkme.c_str(), 20);
+	if (do_swap_alignment) {
 		switch (alignment) {
 		case Align::kLeft:
 			alignment = Align::kRight;

=== modified file 'src/graphic/text_layout.h'
--- src/graphic/text_layout.h	2017-05-13 13:14:29 +0000
+++ src/graphic/text_layout.h	2017-05-21 18:18:51 +0000
@@ -24,6 +24,7 @@
 
 #include "graphic/align.h"
 #include "graphic/color.h"
+#include "graphic/font_handler1.h"
 #include "graphic/image.h"
 #include "graphic/text/font_set.h"
 #include "graphic/text_constants.h"
@@ -38,14 +39,13 @@
   * Returns the exact width of the text rendered as editorfont for the given font size.
   * This function is inefficient; only call when we need the exact width.
   */
-
-uint32_t text_width(const std::string& text, int ptsize);
+int text_width(const std::string& text, int ptsize = UI_FONT_SIZE_SMALL);
 
 /**
-  * Returns the exact height of the text rendered as editorfont for the given font size.
+  * Returns the exact height of the text rendered for the given font size and face.
   * This function is inefficient; only call when we need the exact height.
   */
-uint32_t text_height(const std::string& text, int ptsize);
+int text_height(int ptsize = UI_FONT_SIZE_SMALL, UI::FontSet::Face face = UI::FontSet::Face::kSans);
 
 /**
  * Checks it the given string is RichText or not. Does not do validity checking.
@@ -95,14 +95,14 @@
   * smaller until it fits 'width'. The resulting font size will not go below
   * 'kMinimumFontSize'.
   */
-const Image* autofit_ui_text(const std::string& text,
-                             int width = 0,
-                             RGBColor color = UI_FONT_CLR_FG,
-                             int fontsize = UI_FONT_SIZE_SMALL);
+std::shared_ptr<const UI::RenderedText> autofit_ui_text(const std::string& text,
+                                                        int width = 0,
+                                                        RGBColor color = UI_FONT_CLR_FG,
+                                                        int fontsize = UI_FONT_SIZE_SMALL);
 
 namespace UI {
 
-Align mirror_alignment(Align alignment);
+Align mirror_alignment(Align alignment, const std::string& checkme = "");
 
 void center_vertically(uint32_t h, Vector2i* pt);
 void correct_for_align(Align, uint32_t w, Vector2i* pt);

=== modified file 'src/graphic/texture.cc'
--- src/graphic/texture.cc	2016-10-24 14:07:28 +0000
+++ src/graphic/texture.cc	2017-05-21 18:18:51 +0000
@@ -172,7 +172,7 @@
 void Texture::init(uint16_t w, uint16_t h) {
 	blit_data_ = {
 	   0,  // initialized below
-	   w, h, Rectf(0, 0, w, h),
+	   w, h, Rectf(0.f, 0.f, w, h),
 	};
 	if (w * h == 0) {
 		return;

=== modified file 'src/graphic/texture_atlas.cc'
--- src/graphic/texture_atlas.cc	2017-01-25 18:55:59 +0000
+++ src/graphic/texture_atlas.cc	2017-05-21 18:18:51 +0000
@@ -134,13 +134,13 @@
 	}
 
 	std::unique_ptr<Texture> texture_atlas(new Texture(root->r.w, root->r.h));
-	texture_atlas->fill_rect(Rectf(0, 0, root->r.w, root->r.h), RGBAColor(0, 0, 0, 0));
+	texture_atlas->fill_rect(Rectf(0.f, 0.f, root->r.w, root->r.h), RGBAColor(0, 0, 0, 0));
 
 	const auto packed_texture_id = texture_atlas->blit_data().texture_id;
 	for (Block& block : packed) {
 		texture_atlas->blit(
 		   Rectf(block.node->r.x, block.node->r.y, block.texture->width(), block.texture->height()),
-		   *block.texture, Rectf(0, 0, block.texture->width(), block.texture->height()), 1.,
+		   *block.texture, Rectf(0.f, 0.f, block.texture->width(), block.texture->height()), 1.,
 		   BlendMode::Copy);
 
 		pack_info->emplace_back(PackedTexture(

=== removed file 'src/graphic/texture_cache.cc'
--- src/graphic/texture_cache.cc	2017-01-25 18:55:59 +0000
+++ src/graphic/texture_cache.cc	1970-01-01 00:00:00 +0000
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2006-2017 by the Widelands Development Team
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
- */
-
-#include "graphic/texture_cache.h"
-
-#include <memory>
-
-#include <SDL.h>
-#include <stdint.h>
-
-#include "graphic/texture.h"
-
-// The implementation took inspiration from
-// http://timday.bitbucket.org/lru.html, but our use case here is a little
-// different.
-
-TextureCache::TextureCache(uint32_t max_size_in_bytes)
-   : max_size_in_bytes_(max_size_in_bytes), size_in_bytes_(0) {
-}
-
-TextureCache::~TextureCache() {
-	flush();
-}
-
-void TextureCache::flush() {
-	entries_.clear();
-	access_history_.clear();
-	size_in_bytes_ = 0;
-}
-
-Texture* TextureCache::get(const std::string& hash) {
-	const auto it = entries_.find(hash);
-	if (it == entries_.end())
-		return nullptr;
-
-	// Move this to the back of the access list to signal that we have used this
-	// recently and update last access time.
-	access_history_.splice(access_history_.end(), access_history_, it->second.list_iterator);
-	it->second.last_access = SDL_GetTicks();
-	return it->second.texture.get();
-}
-
-Texture* TextureCache::insert(const std::string& hash, std::unique_ptr<Texture> texture) {
-	assert(entries_.find(hash) == entries_.end());
-
-	const uint32_t texture_size = texture->width() * texture->height() * 4;
-	while (size_in_bytes_ + texture_size > max_size_in_bytes_) {
-		drop();
-	}
-
-	// Record hash as most-recently-used.
-	AccessHistory::iterator it = access_history_.insert(access_history_.end(), hash);
-	size_in_bytes_ += texture_size;
-	return entries_.insert(make_pair(hash, Entry{std::move(texture), SDL_GetTicks(), it}))
-	   .first->second.texture.get();
-}
-
-void TextureCache::drop() {
-	assert(!access_history_.empty());
-
-	// Identify least recently used key
-	const auto it = entries_.find(access_history_.front());
-	assert(it != entries_.end());
-
-	const uint32_t texture_size = it->second.texture->width() * it->second.texture->height() * 4;
-	size_in_bytes_ -= texture_size;
-
-	// Erase both elements to completely purge record
-	entries_.erase(it);
-	access_history_.pop_front();
-}

=== modified file 'src/graphic/wordwrap.cc'
--- src/graphic/wordwrap.cc	2017-05-13 13:14:29 +0000
+++ src/graphic/wordwrap.cc	2017-05-21 18:18:51 +0000
@@ -119,7 +119,7 @@
 	}
 
 	// Optimism: perhaps the entire line fits?
-	if (text_width(text.substr(line_start, orig_end - line_start), fontsize_) <=
+	if (uint32_t(text_width(text.substr(line_start, orig_end - line_start), fontsize_)) <=
 	    wrapwidth_ - safety_margin) {
 		line_end = orig_end;
 		next_line_start = orig_end + 1;
@@ -192,7 +192,8 @@
 	// Now make sure that it really fits.
 	std::string::size_type test_cutoff = line_start + end * 2 / 3;
 	while ((end > 0) && (static_cast<uint32_t>(line_start + end) > test_cutoff)) {
-		if (text_width(text.substr(line_start, end), fontsize_) > wrapwidth_ - safety_margin) {
+		if (uint32_t(text_width(text.substr(line_start, end), fontsize_)) >
+		    wrapwidth_ - safety_margin) {
 			--end;
 		} else {
 			break;
@@ -223,7 +224,7 @@
 	// calc_width_for_wrapping is fast, but it will underestimate the width.
 	// So, we test again with text_width to make sure that the line really fits.
 	return quick_width(i18n::make_ligatures(text.c_str())) <= wrapwidth_ - safety_margin &&
-	       text_width(text, fontsize_) <= wrapwidth_ - safety_margin;
+	       uint32_t(text_width(text, fontsize_)) <= wrapwidth_ - safety_margin;
 }
 
 /**
@@ -247,12 +248,7 @@
  * Compute the total height of the word-wrapped text.
  */
 uint32_t WordWrap::height() const {
-	uint16_t fontheight = 0;
-	if (!lines_.empty()) {
-		fontheight = text_height(lines_[0].text, fontsize_);
-	}
-
-	return fontheight * (lines_.size()) + 2 * kLineMargin;
+	return text_height(fontsize_) * (lines_.size()) + 2 * kLineMargin;
 }
 
 /**
@@ -305,9 +301,9 @@
 
 	Align alignment = mirror_alignment(align);
 
-	uint16_t fontheight = text_height(lines_[0].text, fontsize_);
+	const int fontheight = text_height(fontsize_);
 	for (uint32_t line = 0; line < lines_.size(); ++line, where.y += fontheight) {
-		if (where.y >= dst.height() || int32_t(where.y + fontheight) <= 0)
+		if (where.y >= dst.height() || (where.y + fontheight) <= 0)
 			continue;
 
 		Vector2i point(where.x, where.y);
@@ -316,10 +312,10 @@
 			point.x += wrapwidth_ - kLineMargin;
 		}
 
-		const Image* entry_text_im = UI::g_fh1->render(
+		std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(
 		   as_editorfont(lines_[line].text, fontsize_ - UI::g_fh1->fontset()->size_offset(), color_));
-		UI::correct_for_align(alignment, entry_text_im->width(), &point);
-		dst.blit(point, entry_text_im);
+		UI::correct_for_align(alignment, rendered_text->width(), &point);
+		rendered_text->draw(dst, point);
 
 		if (draw_caret_ && line == caretline) {
 			std::string line_to_caret = lines_[line].text.substr(0, caretpos);

=== modified file 'src/graphic/wordwrap.h'
--- src/graphic/wordwrap.h	2017-04-29 14:57:30 +0000
+++ src/graphic/wordwrap.h	2017-05-21 18:18:51 +0000
@@ -27,8 +27,8 @@
 #include "base/vector.h"
 #include "graphic/align.h"
 #include "graphic/color.h"
+#include "graphic/text/sdl_ttf_font.h"
 #include "graphic/text_constants.h"
-#include "graphic/text/sdl_ttf_font.h"
 
 class RenderTarget;
 

=== modified file 'src/logic/map_objects/map_object.cc'
--- src/logic/map_objects/map_object.cc	2017-05-13 18:48:26 +0000
+++ src/logic/map_objects/map_object.cc	2017-05-21 18:18:51 +0000
@@ -20,6 +20,7 @@
 #include "logic/map_objects/map_object.h"
 
 #include <algorithm>
+#include <cmath>
 #include <cstdarg>
 #include <cstdio>
 #include <cstring>
@@ -463,30 +464,26 @@
 	}
 
 	// Rendering text is expensive, so let's just do it for only a few sizes.
-	scale = std::round(scale);
-	if (scale == 0.f) {
+	// The forumla is a bit fancy to avoid too much text overlap.
+	scale = std::round(2 * (scale > 1.f ? std::sqrt(scale) : std::pow(scale, 2))) / 2;
+	if (scale < 1.f) {
 		return;
 	}
 	const int font_size = scale * UI_FONT_SIZE_SMALL;
 
 	// We always render this so we can have a stable position for the statistics string.
-	const Image* rendered_census_info =
-	   UI::g_fh1->render(as_condensed(census, UI::Align::kCenter, font_size), 120);
-
-	const Vector2i base_pos = field_on_dst.cast<int>() - Vector2i(0, 48) * scale;
-	Vector2i census_pos(base_pos);
-	UI::correct_for_align(UI::Align::kCenter, rendered_census_info->width(), &census_pos);
+	std::shared_ptr<const UI::RenderedText> rendered_census =
+	   UI::g_fh1->render(as_condensed(census, UI::Align::kCenter, font_size), 120 * scale);
+	Vector2i position = field_on_dst.cast<int>() - Vector2i(0, 48) * scale;
 	if (draw_text & TextToDraw::kCensus) {
-		dst->blit(census_pos, rendered_census_info, BlendMode::UseAlpha);
+		rendered_census->draw(*dst, position, UI::Align::kCenter);
 	}
 
 	if (draw_text & TextToDraw::kStatistics && !statictics.empty()) {
-		Vector2i statistics_pos =
-		   base_pos + Vector2i(0, rendered_census_info->height() / 2 + 10 * scale);
-		const Image* rendered_statictics =
+		std::shared_ptr<const UI::RenderedText> rendered_statistics =
 		   UI::g_fh1->render(as_condensed(statictics, UI::Align::kCenter, font_size));
-		UI::correct_for_align(UI::Align::kCenter, rendered_statictics->width(), &statistics_pos);
-		dst->blit(statistics_pos, rendered_statictics, BlendMode::UseAlpha);
+		position.y += rendered_census->height() + text_height(font_size) / 4;
+		rendered_statistics->draw(*dst, position, UI::Align::kCenter);
 	}
 }
 

=== modified file 'src/logic/map_objects/tribes/building.cc'
--- src/logic/map_objects/tribes/building.cc	2017-05-09 09:18:14 +0000
+++ src/logic/map_objects/tribes/building.cc	2017-05-21 18:18:51 +0000
@@ -742,9 +742,11 @@
                             uint32_t throttle_radius) {
 	const std::string& img = descr().representative_image_filename();
 	const int width = descr().representative_image()->width();
+	// NOCOM Remove the background image when testing is done.
 	const std::string rt_description =
 	   (boost::format("<div padding_r=10><p><img width=%d src=%s color=%s></p></div>"
-	                  "<div width=*><p><font size=%d>%s</font></p></div>") %
+	                  "<div background=images/ui_fsmenu/left.png width=*><p><font "
+	                  "size=%d>%s</font></p></div>") %
 	    width % img % owner().get_playercolor().hex_value() % UI_FONT_SIZE_MESSAGE % description)
 	      .str();
 

=== modified file 'src/ui_basic/button.cc'
--- src/ui_basic/button.cc	2017-05-14 14:40:24 +0000
+++ src/ui_basic/button.cc	2017-05-21 18:18:51 +0000
@@ -58,9 +58,7 @@
      clr_down_(229, 161, 2) {
 	// Automatically resize for font height and give it a margin.
 	if (h < 1) {
-		int new_height =
-		   UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
-		   4;
+		int new_height = text_height() + 4;
 		set_desired_size(w, new_height);
 		set_size(w, new_height);
 	}
@@ -207,13 +205,12 @@
 
 	} else if (title_.length()) {
 		//  Otherwise draw title string centered
-		const Image* entry_text_im =
+		std::shared_ptr<const UI::RenderedText> rendered_text =
 		   autofit_ui_text(title_, get_inner_w() - 2 * kButtonImageMargin,
 		                   is_monochrome ? UI_FONT_CLR_DISABLED : UI_FONT_CLR_FG);
 		// Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
-		dst.blit(
-		   Vector2i((get_w() - entry_text_im->width()) / 2, (get_h() - entry_text_im->height()) / 2),
-		   entry_text_im);
+		rendered_text->draw(dst, Vector2i((get_w() - rendered_text->width()) / 2,
+		                                  (get_h() - rendered_text->height()) / 2));
 	}
 
 	//  draw border

=== modified file 'src/ui_basic/checkbox.cc'
--- src/ui_basic/checkbox.cc	2017-04-22 12:19:21 +0000
+++ src/ui_basic/checkbox.cc	2017-05-21 18:18:51 +0000
@@ -45,8 +45,8 @@
    : Panel(parent, p.x, p.y, kStateboxSize, kStateboxSize, tooltip_text),
      flags_(Is_Enabled),
      pic_graphics_(pic),
-     label_text_(""),
-     rendered_text_(nullptr) {
+     rendered_text_(nullptr),
+     label_text_("") {
 	uint16_t w = pic->width();
 	uint16_t h = pic->height();
 	set_desired_size(w, h);
@@ -63,8 +63,8 @@
    : Panel(parent, p.x, p.y, std::max(width, kStateboxSize), kStateboxSize, tooltip_text),
      flags_(Is_Enabled),
      pic_graphics_(g_gr->images().get("images/ui_basic/checkbox_light.png")),
-     label_text_(label_text),
-     rendered_text_(nullptr) {
+     rendered_text_(nullptr),
+     label_text_(label_text) {
 	set_flags(Has_Text, !label_text_.empty());
 	layout();
 }
@@ -83,7 +83,7 @@
 		rendered_text_ = label_text_.empty() ?
 		                    nullptr :
 		                    UI::g_fh1->render(as_uifont(label_text_), text_width(get_w(), pic_width));
-		if (rendered_text_) {
+		if (rendered_text_.get()) {
 			w = std::max(rendered_text_->width() + kPadding + pic_width, w);
 			h = std::max(rendered_text_->height(), h);
 		}
@@ -144,16 +144,16 @@
 	} else {
 		static_assert(0 <= kStateboxSize, "assert(0 <= STATEBOX_WIDTH) failed.");
 		static_assert(0 <= kStateboxSize, "assert(0 <= STATEBOX_HEIGHT) failed.");
-		Vector2i image_anchor(0, 0);
+		Vector2i image_anchor = Vector2i::zero();
 		Vector2i text_anchor(kStateboxSize + kPadding, 0);
 
-		if (rendered_text_) {
+		if (rendered_text_.get()) {
 			if (UI::g_fh1->fontset()->is_rtl()) {
 				text_anchor.x = 0;
 				image_anchor.x = rendered_text_->width() + kPadding;
 				image_anchor.y = (get_h() - kStateboxSize) / 2;
 			}
-			dst.blit(text_anchor, rendered_text_, BlendMode::UseAlpha);
+			rendered_text_->draw(dst, text_anchor);
 		}
 
 		dst.blitrect(

=== modified file 'src/ui_basic/checkbox.h'
--- src/ui_basic/checkbox.h	2017-02-12 09:10:57 +0000
+++ src/ui_basic/checkbox.h	2017-05-21 18:18:51 +0000
@@ -90,8 +90,8 @@
 			flags_ |= flags;
 	}
 	const Image* pic_graphics_;
+	std::shared_ptr<const UI::RenderedText> rendered_text_;
 	const std::string label_text_;
-	const Image* rendered_text_;
 };
 
 /**

=== modified file 'src/ui_basic/editbox.cc'
--- src/ui_basic/editbox.cc	2017-05-13 11:25:24 +0000
+++ src/ui_basic/editbox.cc	2017-05-21 18:18:51 +0000
@@ -80,15 +80,7 @@
                  int margin_y,
                  const Image* background,
                  int font_size)
-   : Panel(parent,
-           x,
-           y,
-           w,
-           h > 0 ? h :
-                   UI::g_fh1->render(as_editorfont(UI::g_fh1->fontset()->representative_character(),
-                                                   font_size))
-                         ->height() +
-                      2 * margin_y),
+   : Panel(parent, x, y, w, h > 0 ? h : text_height(font_size) + 2 * margin_y),
      m_(new EditBoxImpl),
      history_active_(false),
      history_position_(-1) {
@@ -380,15 +372,10 @@
 
 	const int max_width = get_w() - 2 * kMarginX;
 
-	const Image* entry_text_im = UI::g_fh1->render(as_editorfont(m_->text, m_->fontsize));
+	std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_editorfont(m_->text, m_->fontsize));
 
-	const int linewidth = entry_text_im->width();
-	const int lineheight =
-	   m_->text.empty() ?
-	      UI::g_fh1->render(
-	                  as_editorfont(UI::g_fh1->fontset()->representative_character(), m_->fontsize))
-	         ->height() :
-	      entry_text_im->height();
+	const int linewidth = rendered_text->width();
+	const int lineheight = m_->text.empty() ? text_height(m_->fontsize) : rendered_text->height();
 
 	Vector2i point(kMarginX, get_h() / 2);
 	if (m_->align == UI::Align::kRight) {
@@ -405,18 +392,18 @@
 		// We want this always on, e.g. for mixed language savegame filenames
 		if (i18n::has_rtl_character(m_->text.c_str(), 100)) {  // Restrict check for efficiency
 			// TODO(GunChleoc): Arabic: Fix scrolloffset
-			dst.blitrect(point, entry_text_im, Recti(linewidth - max_width, 0, linewidth, lineheight));
+			rendered_text->draw(dst, point, Recti(linewidth - max_width, 0, linewidth, lineheight));
 		} else {
 			if (m_->align == UI::Align::kRight) {
 				// TODO(GunChleoc): Arabic: Fix scrolloffset
-				dst.blitrect(point, entry_text_im,
-				             Recti(point.x + m_->scrolloffset + kMarginX, 0, max_width, lineheight));
+				rendered_text->draw(
+				   dst, point, Recti(point.x + m_->scrolloffset + kMarginX, 0, max_width, lineheight));
 			} else {
-				dst.blitrect(point, entry_text_im, Recti(-m_->scrolloffset, 0, max_width, lineheight));
+				rendered_text->draw(dst, point, Recti(-m_->scrolloffset, 0, max_width, lineheight));
 			}
 		}
 	} else {
-		dst.blitrect(point, entry_text_im, Recti(0, 0, max_width, lineheight));
+		rendered_text->draw(dst, point, Recti(0, 0, max_width, lineheight));
 	}
 
 	if (has_focus()) {
@@ -425,7 +412,7 @@
 		// TODO(GunChleoc): Arabic: Fix cursor position for BIDI text.
 		int caret_x = text_width(line_to_caret, m_->fontsize);
 
-		const uint16_t fontheight = text_height(m_->text, m_->fontsize);
+		const uint16_t fontheight = text_height(m_->fontsize);
 
 		const Image* caret_image = g_gr->images().get("images/ui_basic/caret.png");
 		Vector2i caretpt = Vector2i::zero();

=== modified file 'src/ui_basic/listselect.cc'
--- src/ui_basic/listselect.cc	2017-05-14 14:40:24 +0000
+++ src/ui_basic/listselect.cc	2017-05-21 18:18:51 +0000
@@ -53,9 +53,7 @@
                                const Image* button_background,
                                const ListselectLayout selection_mode)
    : Panel(parent, x, y, w, h),
-     lineheight_(
-        UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
-        kMargin),
+     lineheight_(text_height() + kMargin),
      scrollbar_(this, get_w() - Scrollbar::kSize, 0, Scrollbar::kSize, h, button_background),
      scrollpos_(0),
      selection_(no_selection_index()),
@@ -333,10 +331,10 @@
 	if (selection_mode_ == ListselectLayout::kDropdown) {
 		for (size_t i = 0; i < entry_records_.size(); ++i) {
 			const EntryRecord& er = *entry_records_[i];
-			const Image* entry_text_im = UI::g_fh1->render(as_uifont(
+			std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_uifont(
 			   richtext_escape(er.name), UI_FONT_SIZE_SMALL, er.use_clr ? er.clr : UI_FONT_CLR_FG));
 			int picw = max_pic_width_ ? max_pic_width_ + 10 : 0;
-			int difference = entry_text_im->width() + picw + 8 - get_eff_w();
+			int difference = rendered_text->width() + picw + 8 - get_eff_w();
 			if (difference > 0) {
 				set_size(get_w() + difference, get_h());
 			}
@@ -378,10 +376,10 @@
 		assert(eff_h < std::numeric_limits<int32_t>::max());
 
 		const EntryRecord& er = *entry_records_[idx];
-		const Image* entry_text_im = UI::g_fh1->render(as_uifont(
+		std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_uifont(
 		   richtext_escape(er.name), UI_FONT_SIZE_SMALL, er.use_clr ? er.clr : UI_FONT_CLR_FG));
 
-		int lineheight = std::max(get_lineheight(), entry_text_im->height());
+		int lineheight = std::max(get_lineheight(), rendered_text->height());
 
 		// Don't draw over the bottom edge
 		lineheight = std::min(eff_h - y, lineheight);
@@ -421,23 +419,22 @@
 			         er.pic);
 		}
 
+		// Position the text according to alignment
 		Align alignment = i18n::has_rtl_character(er.name.c_str(), 20) ? Align::kRight : Align::kLeft;
 		if (alignment == UI::Align::kRight) {
 			point.x += maxw - picw;
 		}
 
-		UI::correct_for_align(alignment, entry_text_im->width(), &point);
-
 		// Shift for image width
 		if (!UI::g_fh1->fontset()->is_rtl()) {
 			point.x += picw;
 		}
 
 		// Fix vertical position for mixed font heights
-		if (get_lineheight() > entry_text_im->height()) {
-			point.y += (lineheight_ - entry_text_im->height()) / 2;
+		if (get_lineheight() > rendered_text->height()) {
+			point.y += (lineheight_ - rendered_text->height()) / 2;
 		} else {
-			point.y -= (entry_text_im->height() - lineheight_) / 2;
+			point.y -= (rendered_text->height() - lineheight_) / 2;
 		}
 
 		// Don't draw over the bottom edge
@@ -445,21 +442,7 @@
 		if (lineheight < 0) {
 			break;
 		}
-
-		// Crop to column width while blitting
-		if ((alignment == UI::Align::kRight) &&
-		    (maxw + picw) < static_cast<uint32_t>(entry_text_im->width())) {
-			// Fix positioning for BiDi languages.
-			point.x = 0;
-
-			// We want this always on, e.g. for mixed language savegame filenames, or the languages
-			// list
-			dst.blitrect(point, entry_text_im, Recti(entry_text_im->width() - maxw + picw, 0, maxw,
-			                                         entry_text_im->height()));
-		} else {
-			dst.blitrect(point, entry_text_im, Recti(0, 0, maxw, lineheight));
-		}
-
+		rendered_text->draw(dst, point, Recti(0, 0, maxw, lineheight), alignment, RenderedText::CropMode::kHorizontal);
 		y += get_lineheight();
 		++idx;
 	}

=== modified file 'src/ui_basic/messagebox.cc'
--- src/ui_basic/messagebox.cc	2017-02-23 17:58:25 +0000
+++ src/ui_basic/messagebox.cc	2017-05-21 18:18:51 +0000
@@ -48,14 +48,14 @@
 	const int margin = 5;
 	int width, height = 0;
 	{
-		const Image* temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth);
+		std::shared_ptr<const UI::RenderedText> temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth);
 		width = temp_rendered_text->width();
 		height = temp_rendered_text->height();
 	}
 
 	// Stupid heuristic to avoid excessively long lines
 	if (height < 2 * UI_FONT_SIZE_SMALL) {
-		const Image* temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth / 2);
+		std::shared_ptr<const UI::RenderedText> temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth / 2);
 		width = temp_rendered_text->width();
 		height = temp_rendered_text->height();
 	}

=== modified file 'src/ui_basic/multilineeditbox.cc'
--- src/ui_basic/multilineeditbox.cc	2017-05-13 11:25:24 +0000
+++ src/ui_basic/multilineeditbox.cc	2017-05-21 18:18:51 +0000
@@ -91,7 +91,7 @@
                                    const Image* button_background)
    : Panel(parent, x, y, w, h), d_(new Data(*this, button_background)) {
 	d_->background = background;
-	d_->lineheight = text_height(g_fh1->fontset()->representative_character(), UI_FONT_SIZE_SMALL);
+	d_->lineheight = text_height();
 	set_handle_mouse(true);
 	set_can_focus(true);
 	set_thinks(false);

=== modified file 'src/ui_basic/multilinetextarea.cc'
--- src/ui_basic/multilinetextarea.cc	2017-05-13 11:25:24 +0000
+++ src/ui_basic/multilinetextarea.cc	2017-05-21 18:18:51 +0000
@@ -55,10 +55,7 @@
 
 	scrollbar_.moved.connect(boost::bind(&MultilineTextarea::scrollpos_changed, this, _1));
 
-	scrollbar_.set_singlestepsize(
-	   UI::g_fh1->render(
-	               as_uifont(UI::g_fh1->fontset()->representative_character(), UI_FONT_SIZE_SMALL))
-	      ->height());
+	scrollbar_.set_singlestepsize(text_height());
 	scrollbar_.set_steps(1);
 	scrollbar_.set_force_draw(scrollmode_ == ScrollMode::kScrollNormalForced ||
 	                          scrollmode_ == ScrollMode::kScrollLogForced);
@@ -88,13 +85,10 @@
 	for (int i = 0; i < 2; ++i) {
 		if (!is_richtext(text_)) {
 			use_old_renderer_ = false;
-			const Image* text_im =
-			   UI::g_fh1->render(make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN);
-			height = text_im->height();
+			height = UI::g_fh1->render(make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN)->height();
 		} else if (force_new_renderer_) {
 			use_old_renderer_ = false;
-			const Image* text_im = UI::g_fh1->render(text_, get_eff_w() - 2 * RICHTEXT_MARGIN);
-			height = text_im->height();
+			height = UI::g_fh1->render(text_, get_eff_w() - 2 * RICHTEXT_MARGIN)->height();
 		} else {
 			use_old_renderer_ = true;
 			rt.set_width(get_eff_w() - 2 * RICHTEXT_MARGIN);
@@ -150,19 +144,14 @@
 	if (use_old_renderer_) {
 		rt.draw(dst, Vector2i(RICHTEXT_MARGIN, RICHTEXT_MARGIN - scrollbar_.get_scrollpos()));
 	} else {
-		const Image* text_im;
-		if (!is_richtext(text_)) {
-			text_im = UI::g_fh1->render(make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN);
-		} else {
-			text_im = UI::g_fh1->render(text_, get_eff_w() - 2 * RICHTEXT_MARGIN);
-		}
-
-		uint32_t blit_width = std::min(text_im->width(), static_cast<int>(get_eff_w()));
-		uint32_t blit_height = std::min(text_im->height(), static_cast<int>(get_inner_h()));
+		std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(
+		   is_richtext(text_) ? text_ : make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN);
+		uint32_t blit_width = std::min(rendered_text->width(), static_cast<int>(get_eff_w()));
+		uint32_t blit_height = std::min(rendered_text->height(), get_inner_h());
 
 		if (blit_width > 0 && blit_height > 0) {
 			int anchor = 0;
-			Align alignment = mirror_alignment(align_);
+			Align alignment = mirror_alignment(align_, text_);
 			switch (alignment) {
 			case UI::Align::kCenter:
 				anchor = (get_eff_w() - blit_width) / 2;
@@ -173,10 +162,8 @@
 			case UI::Align::kLeft:
 				anchor = RICHTEXT_MARGIN;
 			}
-
-			dst.blitrect(Vector2i(anchor, 0), text_im,
-			             Recti(0, scrollbar_.get_scrollpos(), blit_width, blit_height),
-			             BlendMode::UseAlpha);
+			rendered_text->draw(dst, Vector2i(anchor, 0),
+			                    Recti(0, scrollbar_.get_scrollpos(), blit_width, blit_height));
 		}
 	}
 }

=== modified file 'src/ui_basic/panel.cc'
--- src/ui_basic/panel.cc	2017-05-13 11:25:24 +0000
+++ src/ui_basic/panel.cc	2017-05-21 18:18:51 +0000
@@ -1060,13 +1060,14 @@
 		text_to_render = as_tooltip(text);
 	}
 
-	static const uint32_t TIP_WIDTH_MAX = 360;
-	const Image* rendered_text = g_fh1->render(text_to_render, TIP_WIDTH_MAX);
-	if (!rendered_text) {
+	constexpr uint32_t kTipWidthMax = 360;
+	std::shared_ptr<const UI::RenderedText> rendered_text = g_fh1->render(text_to_render, kTipWidthMax);
+	if (rendered_text->rects.empty()) {
 		return false;
 	}
-	uint16_t tip_width = rendered_text->width() + 4;
-	uint16_t tip_height = rendered_text->height() + 4;
+
+	const uint16_t tip_width = rendered_text->width() + 4;
+	const uint16_t tip_height = rendered_text->height() + 4;
 
 	Recti r(WLApplication::get()->get_mouse_position() + Vector2i(2, 32), tip_width, tip_height);
 	const Vector2i tooltip_bottom_right = r.opposite_of_origin();
@@ -1078,7 +1079,7 @@
 
 	dst.fill_rect(r, RGBColor(63, 52, 34));
 	dst.draw_rect(r, RGBColor(0, 0, 0));
-	dst.blit(r.origin() + Vector2i(2, 2), rendered_text);
+	rendered_text->draw(dst, r.origin() + Vector2i(2, 2));
 	return true;
 }
 }

=== modified file 'src/ui_basic/panel.h'
--- src/ui_basic/panel.h	2017-02-27 13:53:04 +0000
+++ src/ui_basic/panel.h	2017-05-21 18:18:51 +0000
@@ -28,7 +28,10 @@
 #include <boost/signals2/trackable.hpp>
 
 #include "base/macros.h"
+#include "base/rect.h"
 #include "base/vector.h"
+#include "graphic/align.h"
+#include "graphic/font_handler1.h"
 
 class RenderTarget;
 class Image;

=== modified file 'src/ui_basic/progressbar.cc'
--- src/ui_basic/progressbar.cc	2017-04-22 12:19:21 +0000
+++ src/ui_basic/progressbar.cc	2017-05-21 18:18:51 +0000
@@ -88,10 +88,9 @@
 	const std::string progress_text = (boost::format("<font color=%s>%u%%</font>") %
 	                                   UI_FONT_CLR_BRIGHT.hex_value() % floor(fraction * 100.f))
 	                                     .str();
-	const Image* rendered_text = UI::g_fh1->render(as_uifont(progress_text));
+	std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_uifont(progress_text));
 	Vector2i pos(get_w() / 2, get_h() / 2);
-	UI::correct_for_align(UI::Align::kCenter, rendered_text->width(), &pos);
 	UI::center_vertically(rendered_text->height(), &pos);
-	dst.blit(pos, rendered_text, BlendMode::UseAlpha);
+	rendered_text->draw(dst, pos, UI::Align::kCenter);
 }
 }

=== modified file 'src/ui_basic/progresswindow.cc'
--- src/ui_basic/progresswindow.cc	2017-05-13 18:48:26 +0000
+++ src/ui_basic/progresswindow.cc	2017-05-21 18:18:51 +0000
@@ -63,8 +63,7 @@
 	label_center_.x = get_w() / 2;
 	label_center_.y = get_h() * PROGRESS_LABEL_POSITION_Y / 100;
 
-	const uint32_t h =
-	   UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height();
+	const uint32_t h = text_height();
 
 	label_rectangle_.x = get_w() / 4;
 	label_rectangle_.w = get_w() / 2;
@@ -99,11 +98,10 @@
 	draw(rt);
 
 	rt.fill_rect(label_rectangle_, PROGRESS_FONT_COLOR_BG);
-	const Image* rendered_text =
+	std::shared_ptr<const UI::RenderedText> rendered_text =
 	   UI::g_fh1->render(as_uifont(description, UI_FONT_SIZE_SMALL, PROGRESS_FONT_COLOR_FG));
-	UI::correct_for_align(UI::Align::kCenter, rendered_text->width(), &label_center_);
 	UI::center_vertically(rendered_text->height(), &label_center_);
-	rt.blit(label_center_, rendered_text, BlendMode::UseAlpha);
+	rendered_text->draw(rt, label_center_, UI::Align::kCenter);
 
 #ifdef _WIN32
 	// Pump events to prevent "not responding" on windows

=== modified file 'src/ui_basic/slider.cc'
--- src/ui_basic/slider.cc	2017-04-22 12:19:21 +0000
+++ src/ui_basic/slider.cc	2017-05-21 18:18:51 +0000
@@ -508,12 +508,7 @@
             w / (2 * labels_in.size()) - cursor_size / 2,
             0,
             w - (w / labels_in.size()) + cursor_size,
-            h -
-               UI::g_fh1->render(as_condensed(UI::g_fh1->fontset()->representative_character(),
-                                              UI::Align::kLeft,
-                                              UI_FONT_SIZE_SMALL - 2))
-                  ->height() -
-               2,
+            h - text_height(UI_FONT_SIZE_SMALL - 2, UI::FontSet::Face::kCondensed) - 2,
             0,
             labels_in.size() - 1,
             value_,
@@ -538,11 +533,10 @@
 	uint32_t gap_n = get_w() / labels.size();
 
 	for (uint32_t i = 0; i < labels.size(); i++) {
-		const Image* rendered_text =
+		std::shared_ptr<const UI::RenderedText> rendered_text =
 		   UI::g_fh1->render(as_condensed(labels[i], UI::Align::kCenter, UI_FONT_SIZE_SMALL - 2));
-		Vector2i point(gap_1 + i * gap_n, get_h() - rendered_text->height());
-		UI::correct_for_align(UI::Align::kCenter, rendered_text->width(), &point);
-		dst.blit(point, rendered_text, BlendMode::UseAlpha);
+		rendered_text->draw(
+		   dst, Vector2i(gap_1 + i * gap_n, get_h() - rendered_text->height()), UI::Align::kCenter);
 	}
 }
 
@@ -557,13 +551,8 @@
 	uint32_t h = get_h();
 	assert(labels.size());
 	slider.set_pos(Vector2i(w / (2 * labels.size()) - slider.cursor_size_ / 2, 0));
-	slider.set_size(
-	   w - (w / labels.size()) + slider.cursor_size_,
-	   h -
-	      UI::g_fh1->render(as_condensed(UI::g_fh1->fontset()->representative_character(),
-	                                     UI::Align::kLeft, UI_FONT_SIZE_SMALL - 2))
-	         ->height() +
-	      2);
+	slider.set_size(w - (w / labels.size()) + slider.cursor_size_,
+	                h - text_height(UI_FONT_SIZE_SMALL - 2, UI::FontSet::Face::kCondensed) + 2);
 	Panel::layout();
 }
 }

=== modified file 'src/ui_basic/table.cc'
--- src/ui_basic/table.cc	2017-05-14 14:40:24 +0000
+++ src/ui_basic/table.cc	2017-05-21 18:18:51 +0000
@@ -51,11 +51,8 @@
                     TableRows rowtype)
    : Panel(parent, x, y, w, h),
      total_width_(0),
-     headerheight_(
-        UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
-        4),
-     lineheight_(
-        UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height()),
+     headerheight_(text_height() + 4),
+     lineheight_(text_height()),
      button_background_(button_background),
      scrollbar_(nullptr),
      scrollbar_filler_button_(
@@ -235,7 +232,7 @@
 		Columns::size_type const nr_columns = columns_.size();
 		for (uint32_t i = 0, curx = 0; i < nr_columns; ++i) {
 			const Column& column = columns_[i];
-			int const curw = column.width;
+			const int curw = column.width;
 			Align alignment = mirror_alignment(column.alignment);
 
 			const Image* entry_picture = er.get_picture(i);
@@ -288,52 +285,37 @@
 					}
 					dst.blit(Vector2i(draw_x, point.y + (lineheight - pich) / 2), entry_picture);
 				}
-				point.x += picw;
+				if (alignment != Align::kRight) {
+					point.x += picw;
+				}
 			}
 
-			++picw;  // A bit of margin between image and text
-
 			if (entry_string.empty()) {
 				curx += curw;
 				continue;
 			}
-			const Image* entry_text_im = UI::g_fh1->render(as_uifont(richtext_escape(entry_string)));
-
+			std::shared_ptr<const UI::RenderedText> rendered_text =
+			   UI::g_fh1->render(as_uifont(richtext_escape(entry_string)));
+
+			// Fix text alignment for BiDi languages if the entry contains an RTL character. We want
+			// this always on, e.g. for mixed language savegame filenames.
+			alignment = mirror_alignment(column.alignment, entry_string);
+
+			// Position the text according to alignment
 			switch (alignment) {
 			case UI::Align::kCenter:
 				point.x += (curw - picw) / 2;
 				break;
 			case UI::Align::kRight:
-				point.x += curw - 2 * picw;
+				point.x += curw - picw;
 				break;
 			case UI::Align::kLeft:
 				break;
 			}
 
-			// Add an offset for rightmost column when the scrollbar is shown.
-			int text_width = entry_text_im->width();
-			if (i == nr_columns - 1 && scrollbar_->is_enabled()) {
-				text_width = text_width + scrollbar_->get_w();
-			}
-			UI::correct_for_align(alignment, text_width, &point);
-
-			// Crop to column width while blitting
-			if ((curw + picw) < text_width) {
-				// Fix positioning for BiDi languages.
-				if (UI::g_fh1->fontset()->is_rtl()) {
-					point.x = (alignment == UI::Align::kRight) ? curx : curx + picw;
-				}
-				// We want this always on, e.g. for mixed language savegame filenames
-				if (i18n::has_rtl_character(
-				       entry_string.c_str(), 20)) {  // Restrict check for efficiency
-					dst.blitrect(
-					   point, entry_text_im, Recti(text_width - curw + picw, 0, text_width, lineheight));
-				} else {
-					dst.blitrect(point, entry_text_im, Recti(0, 0, curw - picw, lineheight));
-				}
-			} else {
-				dst.blitrect(point, entry_text_im, Recti(0, 0, curw - picw, lineheight));
-			}
+			constexpr int kMargin = 1;
+			rendered_text->draw(dst, point, Recti(kMargin, 0, curw - picw - 2 * kMargin, lineheight),
+			                    alignment, RenderedText::CropMode::kHorizontal);
 			curx += curw;
 		}
 

=== modified file 'src/ui_basic/tabpanel.cc'
--- src/ui_basic/tabpanel.cc	2017-05-13 11:25:24 +0000
+++ src/ui_basic/tabpanel.cc	2017-05-21 18:18:51 +0000
@@ -47,19 +47,23 @@
 Tab::Tab(TabPanel* const tab_parent,
          size_t const tab_id,
          int32_t x,
-         int32_t w,
          const std::string& name,
          const std::string& init_title,
          const Image* init_pic,
          const std::string& tooltip_text,
          Panel* const contents)
-   : NamedPanel(tab_parent, name, x, 0, w, kTabPanelButtonHeight, tooltip_text),
+   : NamedPanel(tab_parent, name, x, 0, kTabPanelButtonHeight, kTabPanelButtonHeight, tooltip_text),
      parent(tab_parent),
      id(tab_id),
      pic(init_pic),
-     title(init_title),
+     rendered_title(nullptr),
      tooltip(tooltip_text),
      panel(contents) {
+	if (!init_title.empty()) {
+		rendered_title = UI::g_fh1->render(as_uifont(init_title));
+		set_size(std::max(kTabPanelButtonHeight, rendered_title->width() + 2 * kTabPanelTextMargin),
+		         kTabPanelButtonHeight);
+	}
 }
 
 /**
@@ -165,9 +169,7 @@
                        const std::string& title,
                        Panel* const panel,
                        const std::string& tooltip_text) {
-	const Image* pic = UI::g_fh1->render(as_uifont(title));
-	return add_tab(std::max(kTabPanelButtonHeight, pic->width() + 2 * kTabPanelTextMargin), name,
-	               title, pic, tooltip_text, panel);
+	return add_tab(name, title, nullptr, tooltip_text, panel);
 }
 
 /**
@@ -177,12 +179,11 @@
                        const Image* pic,
                        Panel* const panel,
                        const std::string& tooltip_text) {
-	return add_tab(kTabPanelButtonHeight, name, "", pic, tooltip_text, panel);
+	return add_tab(name, "", pic, tooltip_text, panel);
 }
 
 /** Common adding function for textual and pictorial tabs. */
-uint32_t TabPanel::add_tab(int32_t width,
-                           const std::string& name,
+uint32_t TabPanel::add_tab(const std::string& name,
                            const std::string& title,
                            const Image* pic,
                            const std::string& tooltip_text,
@@ -192,7 +193,7 @@
 
 	size_t id = tabs_.size();
 	int32_t x = id > 0 ? tabs_[id - 1]->get_x() + tabs_[id - 1]->get_w() : 0;
-	tabs_.push_back(new Tab(this, id, x, width, name, title, pic, tooltip_text, panel));
+	tabs_.push_back(new Tab(this, id, x, name, title, pic, tooltip_text, panel));
 
 	// Add a margin if there is a border
 	if (border_type_ == TabPanel::Type::kBorder) {
@@ -282,10 +283,8 @@
 			dst.brighten_rect(Recti(x, 0, tab_width, kTabPanelButtonHeight), MOUSE_OVER_BRIGHT_FACTOR);
 		}
 
-		assert(tabs_[idx]->pic);
-
-		// If the title is empty, we will assume a pictorial tab
-		if (tabs_[idx]->title.empty()) {
+		// If pic is there, we will assume a pictorial tab
+		if (tabs_[idx]->pic != nullptr) {
 			// Scale the image down if needed, but keep the ratio.
 			constexpr int kMaxImageSize = kTabPanelButtonHeight - 2 * kTabPanelImageMargin;
 			double image_scale =
@@ -299,10 +298,10 @@
 			         (kTabPanelButtonHeight - picture_height) / 2, picture_width, picture_height),
 			   tabs_[idx]->pic, Recti(0, 0, tabs_[idx]->pic->width(), tabs_[idx]->pic->height()), 1,
 			   BlendMode::UseAlpha);
-		} else {
-			dst.blit(Vector2i(x + kTabPanelTextMargin,
-			                  (kTabPanelButtonHeight - tabs_[idx]->pic->height()) / 2),
-			         tabs_[idx]->pic, BlendMode::UseAlpha);
+		} else if (tabs_[idx]->rendered_title != nullptr) {
+			tabs_[idx]->rendered_title->draw(
+			   dst, Vector2i(x + kTabPanelTextMargin,
+			                 (kTabPanelButtonHeight - tabs_[idx]->rendered_title->height()) / 2));
 		}
 
 		// Draw top part of border

=== modified file 'src/ui_basic/tabpanel.h'
--- src/ui_basic/tabpanel.h	2017-02-12 09:10:57 +0000
+++ src/ui_basic/tabpanel.h	2017-05-21 18:18:51 +0000
@@ -46,7 +46,6 @@
 	Tab(TabPanel* parent,
 	    size_t id,
 	    int32_t x,
-	    int32_t w,
 	    const std::string& name,
 	    const std::string& title,
 	    const Image* pic,
@@ -68,7 +67,7 @@
 	uint32_t id;
 
 	const Image* pic;
-	std::string title;
+	std::shared_ptr<const UI::RenderedText> rendered_title;
 	std::string tooltip;
 	Panel* panel;
 };
@@ -136,8 +135,7 @@
 
 private:
 	// Common adding function for textual and pictorial tabs
-	uint32_t add_tab(int32_t width,
-	                 const std::string& name,
+	uint32_t add_tab(const std::string& name,
 	                 const std::string& title,
 	                 const Image* pic,
 	                 const std::string& tooltip,

=== modified file 'src/ui_basic/textarea.cc'
--- src/ui_basic/textarea.cc	2017-04-22 10:17:39 +0000
+++ src/ui_basic/textarea.cc	2017-05-21 18:18:51 +0000
@@ -127,8 +127,7 @@
 	if (!text_.empty()) {
 		Vector2i anchor(
 		   (align_ == Align::kCenter) ? get_w() / 2 : (align_ == UI::Align::kRight) ? get_w() : 0, 0);
-		UI::correct_for_align(align_, rendered_text_->width(), &anchor);
-		dst.blit(anchor, rendered_text_, BlendMode::UseAlpha);
+		rendered_text_->draw(dst, anchor, align_);
 	}
 }
 
@@ -188,14 +187,12 @@
 	uint32_t w = 0;
 	uint16_t h = 0;
 
-	if (rendered_text_) {
+	if (rendered_text_.get()) {
 		w = fixed_width_ > 0 ? fixed_width_ : rendered_text_->width();
 		h = rendered_text_->height();
 		// We want empty textareas to have height
 		if (text_.empty()) {
-			h = UI::g_fh1->render(
-			                as_uifont(UI::g_fh1->fontset()->representative_character(), fontsize_))
-			       ->height();
+			h = text_height(fontsize_);
 		}
 	}
 	set_desired_size(w, h);

=== modified file 'src/ui_basic/textarea.h'
--- src/ui_basic/textarea.h	2017-02-23 19:38:51 +0000
+++ src/ui_basic/textarea.h	2017-05-21 18:18:51 +0000
@@ -94,7 +94,7 @@
 
 	LayoutMode layoutmode_;
 	std::string text_;
-	const Image* rendered_text_;
+	std::shared_ptr<const UI::RenderedText> rendered_text_;
 	Align align_;
 	RGBColor color_;
 	int fontsize_;

=== modified file 'src/ui_basic/window.cc'
--- src/ui_basic/window.cc	2017-05-13 11:25:24 +0000
+++ src/ui_basic/window.cc	2017-05-21 18:18:51 +0000
@@ -272,14 +272,13 @@
 	// draw the title if we have one
 	if (!title_.empty()) {
 		// The title shouldn't be richtext, but we escape it just to make sure.
-		const Image* text =
+		std::shared_ptr<const UI::RenderedText> text =
 		   autofit_ui_text(richtext_escape(title_), get_inner_w(), UI_FONT_CLR_FG, 13);
 
 		// Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
 		Vector2i pos(get_lborder() + get_inner_w() / 2, TP_B_PIXMAP_THICKNESS / 2);
-		UI::correct_for_align(UI::Align::kCenter, text->width(), &pos);
 		UI::center_vertically(text->height(), &pos);
-		dst.blit(pos, text, BlendMode::UseAlpha);
+		text->draw(dst, pos, UI::Align::kCenter);
 	}
 
 	if (!is_minimal_) {

=== modified file 'src/ui_fsmenu/loadgame.cc'
--- src/ui_fsmenu/loadgame.cc	2017-03-05 17:55:29 +0000
+++ src/ui_fsmenu/loadgame.cc	2017-05-21 18:18:51 +0000
@@ -332,19 +332,9 @@
 }
 
 std::string FullscreenMenuLoadGame::filename_list_string() {
-	std::set<uint32_t> selections = table_.selections();
 	boost::format message;
-	int counter = 0;
-	for (const uint32_t index : selections) {
-		++counter;
-		// TODO(GunChleoc): We can exceed the texture size for the font renderer,
-		// so we have to restrict this for now.
-		if (counter > 50) {
-			message = boost::format("%s\n%s") % message % "...";
-			break;
-		}
+	for (const uint32_t index : table_.selections()) {
 		const SavegameData& gamedata = games_data_[table_.get(table_.get_record(index))];
-
 		if (gamedata.errormessage.empty()) {
 			message =
 			   boost::format("%s\n%s") % message %

=== modified file 'src/wui/chatoverlay.cc'
--- src/wui/chatoverlay.cc	2017-04-22 12:19:21 +0000
+++ src/wui/chatoverlay.cc	2017-05-21 18:18:51 +0000
@@ -167,15 +167,15 @@
 	if (!m->havemessages_)
 		return;
 
-	const Image* im = nullptr;
+	std::shared_ptr<const UI::RenderedText> im = std::shared_ptr<const UI::RenderedText>(nullptr);
 	try {
 		im = UI::g_fh1->render(m->all_text_, get_w());
 	} catch (RT::WidthTooSmall&) {
 		// Oops, maybe one long word? We render again, not limiting the width, but
 		// render everything in one single line.
-		im = UI::g_fh1->render(m->all_text_, 0);
+		im = UI::g_fh1->render(m->all_text_);
 	}
-	assert(im != nullptr);
+	assert(im.get() != nullptr);
 
 	// Background
 	const int32_t height = im->height() > get_h() ? get_h() : im->height();
@@ -185,8 +185,6 @@
 	if (!m->transparent_) {
 		dst.fill_rect(Recti(0, top, width, height), RGBAColor(50, 50, 50, 128), BlendMode::Default);
 	}
-	int32_t topcrop = im->height() - height;
-	Recti cropRect(0, topcrop, width, height);
-
-	dst.blitrect(Vector2i(0, top), im, cropRect);
+	const int topcrop = im->height() - height;
+	im->draw(dst, Vector2i(0, top), Recti(0, topcrop, width, height));
 }

=== modified file 'src/wui/game_tips.cc'
--- src/wui/game_tips.cc	2017-05-13 18:48:26 +0000
+++ src/wui/game_tips.cc	2017-05-21 18:18:51 +0000
@@ -99,20 +99,17 @@
 }
 
 void GameTips::show_tip(int32_t index) {
-	// try to load a background
+	RenderTarget& rt = *g_gr->get_render_target();
+
 	const Image* pic_background = g_gr->images().get(BG_IMAGE);
-	assert(pic_background);
-
-	RenderTarget& rt = *g_gr->get_render_target();
-
-	uint16_t w = pic_background->width();
-	uint16_t h = pic_background->height();
+	const int w = pic_background->width();
+	const int h = pic_background->height();
 	Vector2i pt((g_gr->get_xres() - w) / 2, (g_gr->get_yres() - h) / 2);
-	Recti tips_area(pt, w, h);
 	rt.blit(pt, pic_background);
 
-	const Image* rendered_text = UI::g_fh1->render(as_game_tip(tips_[index].text), tips_area.w);
-	rt.blit(tips_area.center().cast<int>() -
-	           Vector2i(rendered_text->width() / 2, rendered_text->height() / 2),
-	        rendered_text);
+	std::shared_ptr<const UI::RenderedText> rendered_text =
+	   UI::g_fh1->render(as_game_tip(tips_[index].text), w);
+	pt = Vector2i((g_gr->get_xres() - rendered_text->width()) / 2,
+	              (g_gr->get_yres() - rendered_text->height()) / 2);
+	rendered_text->draw(rt, pt);
 }

=== modified file 'src/wui/interactive_base.cc'
--- src/wui/interactive_base.cc	2017-05-14 14:40:24 +0000
+++ src/wui/interactive_base.cc	2017-05-21 18:18:51 +0000
@@ -356,8 +356,8 @@
 		std::string node_text;
 		if (is_game) {
 			const std::string gametime(gametimestring(egbase().get_gametime(), true));
-			const std::string gametime_text = as_condensed(gametime);
-			dst.blit(Vector2i(5, 5), UI::g_fh1->render(gametime_text), BlendMode::UseAlpha);
+			std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_condensed(gametime));
+			rendered_text->draw(dst, Vector2i(5, 5));
 
 			static boost::format node_format("(%i, %i)");
 			node_text = as_condensed((node_format % sel_.pos.node.x % sel_.pos.node.y).str());
@@ -366,20 +366,17 @@
 			const int32_t height = map[sel_.pos.node].get_height();
 			node_text = as_condensed((node_format % sel_.pos.node.x % sel_.pos.node.y % height).str());
 		}
-
-		const Image* rendered_text = UI::g_fh1->render(node_text);
-		Vector2i point(get_w() - 5, get_h() - rendered_text->height() - 5);
-		UI::correct_for_align(UI::Align::kRight, rendered_text->width(), &point);
-		dst.blit(point, rendered_text, BlendMode::UseAlpha);
+		std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(node_text);
+		rendered_text->draw(
+		   dst, Vector2i(get_w() - 5, get_h() - rendered_text->height() - 5), UI::Align::kRight);
 	}
 
 	// Blit FPS when playing a game in debug mode.
 	if (get_display_flag(dfDebug) && is_game) {
 		static boost::format fps_format("%5.1f fps (avg: %5.1f fps)");
-		const Image* rendered_text = UI::g_fh1->render(as_condensed(
+		std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_condensed(
 		   (fps_format % (1000.0 / frametime_) % (1000.0 / (avg_usframetime_ / 1000))).str()));
-		dst.blit(
-		   Vector2i((get_w() - rendered_text->width()) / 2, 5), rendered_text, BlendMode::UseAlpha);
+		rendered_text->draw(dst, Vector2i((get_w() - rendered_text->width()) / 2, 5));
 	}
 }
 

=== modified file 'src/wui/interactive_base.h'
--- src/wui/interactive_base.h	2017-03-03 18:13:55 +0000
+++ src/wui/interactive_base.h	2017-05-21 18:18:51 +0000
@@ -29,11 +29,11 @@
 #include "logic/map.h"
 #include "notifications/notifications.h"
 #include "profile/profile.h"
+#include "sound/note_sound.h"
+#include "sound/sound_handler.h"
 #include "ui_basic/box.h"
 #include "ui_basic/textarea.h"
 #include "ui_basic/unique_window.h"
-#include "sound/note_sound.h"
-#include "sound/sound_handler.h"
 #include "wui/chatoverlay.h"
 #include "wui/debugconsole.h"
 #include "wui/edge_overlay_manager.h"

=== modified file 'src/wui/interactive_gamebase.cc'
--- src/wui/interactive_gamebase.cc	2017-04-22 10:17:39 +0000
+++ src/wui/interactive_gamebase.cc	2017-05-21 18:18:51 +0000
@@ -126,10 +126,8 @@
 		}
 
 		if (!game_speed.empty()) {
-			Vector2i point(get_w() - 5, 5);
-			const Image* rendered_speed = UI::g_fh1->render(game_speed);
-			UI::correct_for_align(UI::Align::kRight, rendered_speed->width(), &point);
-			dst.blit(point, rendered_speed, BlendMode::UseAlpha);
+			std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(game_speed);
+			rendered_text->draw(dst, Vector2i(get_w() - 5, 5), UI::Align::kRight);
 		}
 	}
 }

=== modified file 'src/wui/mapdetails.cc'
--- src/wui/mapdetails.cc	2017-02-23 17:58:25 +0000
+++ src/wui/mapdetails.cc	2017-05-21 18:18:51 +0000
@@ -102,9 +102,7 @@
 }
 
 void MapDetails::layout() {
-	name_label_.set_size(
-	   get_w() - padding_,
-	   UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() + 2);
+	name_label_.set_size(get_w() - padding_, text_height() + 2);
 
 	// Adjust sizes for show / hide suggested teams
 	if (suggested_teams_box_->is_visible()) {

=== modified file 'src/wui/maptable.cc'
--- src/wui/maptable.cc	2017-03-05 17:55:29 +0000
+++ src/wui/maptable.cc	2017-05-21 18:18:51 +0000
@@ -33,7 +33,7 @@
 	add_column(35, _("Pl."), _("Number of players"), UI::Align::kCenter);
 	add_column(0, _("Filename"), _("The name of the map or scenario"), UI::Align::kLeft,
 	           UI::TableColumnType::kFlexible);
-	add_column(115, _("Size"), _("The size of the map (Width x Height)"));
+	add_column(90, _("Size"), _("The size of the map (Width x Height)"));
 	set_sort_column(0);
 }
 

=== modified file 'src/wui/plot_area.cc'
--- src/wui/plot_area.cc	2017-05-13 13:14:29 +0000
+++ src/wui/plot_area.cc	2017-05-21 18:18:51 +0000
@@ -178,17 +178,15 @@
                 const RGBColor& color,
                 const Vector2i& pos,
                 RenderTarget& dst) {
-	const Image* pic = UI::g_fh1->render(ytick_text_style(value, color));
+	std::shared_ptr<const UI::RenderedText> tick = UI::g_fh1->render(ytick_text_style(value, color));
 	Vector2i point(pos);  // Un-const this
-	UI::correct_for_align(UI::Align::kRight, pic->width(), &point);
-	UI::center_vertically(pic->height(), &point);
-	dst.blit(point, pic, BlendMode::UseAlpha);
+	UI::center_vertically(tick->height(), &point);
+	tick->draw(dst, point, UI::Align::kRight);
 }
 
 uint32_t calc_plot_x_max_ticks(int32_t plot_width) {
 	// Render a number with 3 digits (maximal length which should appear)
-	const Image* pic = UI::g_fh1->render(ytick_text_style(" -888 ", kAxisLineColor));
-	return plot_width / pic->width();
+	return plot_width / UI::g_fh1->render(ytick_text_style(" -888 ", kAxisLineColor))->width();
 }
 
 int calc_slider_label_width(const std::string& label) {
@@ -264,12 +262,11 @@
 
 		// The space at the end is intentional to have the tick centered
 		// over the number, not to the left
-		const Image* xtick = UI::g_fh1->render(
+		std::shared_ptr<const UI::RenderedText> xtick = UI::g_fh1->render(
 		   xtick_text_style((boost::format("-%u ") % (max_x / how_many_ticks * i)).str()));
 		Vector2i pos(posx, inner_h - kSpaceBottom + 10);
-		UI::correct_for_align(UI::Align::kCenter, xtick->width(), &pos);
 		UI::center_vertically(xtick->height(), &pos);
-		dst.blit(pos, xtick, BlendMode::UseAlpha);
+		xtick->draw(dst, pos, UI::Align::kCenter);
 
 		posx -= sub;
 	}
@@ -284,10 +281,10 @@
 	   kAxisLineColor, kAxisLinesWidth);
 
 	//  print the used unit
-	const Image* xtick = UI::g_fh1->render(xtick_text_style(get_generic_unit_name(unit)));
+	std::shared_ptr<const UI::RenderedText> xtick = UI::g_fh1->render(xtick_text_style(get_generic_unit_name(unit)));
 	Vector2i pos(2, kSpacing + 2);
 	UI::center_vertically(xtick->height(), &pos);
-	dst.blit(pos, xtick, BlendMode::UseAlpha);
+	xtick->draw(dst, pos);
 }
 
 }  // namespace

=== modified file 'src/wui/waresdisplay.cc'
--- src/wui/waresdisplay.cc	2017-05-13 13:14:29 +0000
+++ src/wui/waresdisplay.cc	2017-05-21 18:18:51 +0000
@@ -331,11 +331,10 @@
 	dst.fill_rect(Recti(p + Vector2i(0, WARE_MENU_PIC_HEIGHT), w, WARE_MENU_INFO_SIZE),
 	              info_color_for_ware(id));
 
-	const Image* text = UI::g_fh1->render(as_waresinfo(info_for_ware(id)));
-	if (text)  // might be zero when there is no info text.
-		dst.blit(p + Vector2i(w - text->width() - 1,
-		                      WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + 1 - text->height()),
-		         text);
+	std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_waresinfo(info_for_ware(id)));
+	rendered_text->draw(
+	   dst, Vector2i(p.x + w - rendered_text->width() - 1,
+	                 p.y + WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + 1 - rendered_text->height()));
 }
 
 // Wares highlighting/selecting


Follow ups