← 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 supported size when MultilineTextareas get arbitrarily long.

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

Requested reviews:
  Widelands Developers (widelands-dev)

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 requested to review the proposed merge of lp:~widelands-dev/widelands/fh1-multitexture into lp:widelands.
=== modified file 'src/editor/ui_menus/main_menu_map_options.cc'
--- src/editor/ui_menus/main_menu_map_options.cc	2017-02-26 11:00:07 +0000
+++ src/editor/ui_menus/main_menu_map_options.cc	2017-05-11 13:11:27 +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-02-26 11:00:07 +0000
+++ src/editor/ui_menus/main_menu_random_map.cc	2017-05-11 13:11:27 +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/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-11 13:11:27 +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_handler.cc'
--- src/graphic/font_handler.cc	2017-02-28 20:07:07 +0000
+++ src/graphic/font_handler.cc	2017-05-11 13:11:27 +0000
@@ -54,9 +54,9 @@
 	int caret_x = style.calc_bare_width(text.substr(0, caret_offset));
 
 	const Image* caret_image = g_gr->images().get("images/ui_basic/caret.png");
-	Vector2f caretpt;
+	Vector2i caretpt;
 	caretpt.x = dstpoint.x + caret_x + LINE_MARGIN - caret_image->width();
-	caretpt.y = dstpoint.y + (style.font->height() - caret_image->height()) / 2.f;
+	caretpt.y = dstpoint.y + (style.font->height() - caret_image->height()) / 2;
 	dst.blit(caretpt, caret_image);
 }
 
@@ -203,7 +203,7 @@
  */
 void FontHandler::draw_text(RenderTarget& dst,
                             const TextStyle& style,
-                            Vector2i dstpoint_i,
+                            Vector2i dstpoint,
                             const std::string& text,
                             Align align,
                             uint32_t caret) {
@@ -213,14 +213,13 @@
 	copytext = i18n::make_ligatures(copytext.c_str());
 	const LineCacheEntry& lce = d->get_line(style, copytext);
 
-	Vector2f dstpoint = dstpoint_i.cast<float>();
 	UI::correct_for_align(align, lce.width + 2 * LINE_MARGIN, &dstpoint);
 
 	if (lce.image)
-		dst.blit(Vector2f(dstpoint.x + LINE_MARGIN, dstpoint.y), lce.image.get());
+		dst.blit(Vector2i(dstpoint.x + LINE_MARGIN, dstpoint.y), lce.image.get());
 
 	if (caret <= copytext.size())
-		draw_caret(dst, style, dstpoint, copytext, caret);
+		draw_caret(dst, style, dstpoint.cast<float>(), copytext, caret);
 }
 
 /**
@@ -233,7 +232,7 @@
 	const LineCacheEntry& lce = d->get_line(style, text);
 
 	if (lce.image) {
-		dst.blit(dstpoint.cast<float>(), lce.image.get());
+		dst.blit(dstpoint, lce.image.get());
 	}
 
 	return lce.width;

=== 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-11 13:11:27 +0000
@@ -50,57 +50,8 @@
 // 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_;
-};
-}
+constexpr uint32_t kTextureCacheSize = 30 << 20;  // shifting converts to MB
+}  // namespace
 
 namespace UI {
 
@@ -110,26 +61,23 @@
 class FontHandler1 : public IFontHandler1 {
 public:
 	FontHandler1(ImageCache* image_cache, const std::string& locale)
-	   : texture_cache_(new TextureCache(RICHTEXT_TEXTURE_CACHE)),
+	   : texture_cache_(new TextureCache(kTextureCacheSize)),
 	     fontsets_(),
 	     fontset_(fontsets_.get_fontset(locale)),
 	     rt_renderer_(new RT::Renderer(image_cache, texture_cache_.get(), fontsets_)),
 	     image_cache_(image_cache) {
 	}
 	virtual ~FontHandler1() {
+		render_cache_.clear();
 	}
 
-	const Image* render(const string& text, uint16_t w = 0) override {
+	const RenderedText* 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));
+		if (render_cache_.count(hash) != 1) {
+			render_cache_.insert(std::make_pair(hash, std::unique_ptr<const RenderedText>(std::move(rt_renderer_->render(text, w)))));
+		}
+		assert(render_cache_.count(hash) == 1);
+		return render_cache_.find(hash)->second.get();
 	}
 
 	UI::FontSet const* fontset() const override {
@@ -139,6 +87,7 @@
 	void reinitialize_fontset(const std::string& locale) override {
 		fontset_ = fontsets_.get_fontset(locale);
 		texture_cache_.get()->flush();
+		render_cache_.clear();
 		rt_renderer_.reset(new RT::Renderer(image_cache_, texture_cache_.get(), fontsets_));
 	}
 
@@ -148,6 +97,7 @@
 	UI::FontSet const* fontset_;  // The currently active FontSet
 	std::unique_ptr<RT::Renderer> rt_renderer_;
 	ImageCache* const image_cache_;  // not owned
+	std::unordered_map<std::string, std::unique_ptr<const RenderedText>> render_cache_;
 };
 
 IFontHandler1* create_fonthandler(ImageCache* image_cache, const std::string& locale) {

=== 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-11 13:11:27 +0000
@@ -23,11 +23,13 @@
 
 #include <memory>
 #include <string>
+#include <unordered_map>
 
 #include "base/macros.h"
 #include "base/vector.h"
 #include "graphic/align.h"
 #include "graphic/text/font_set.h"
+#include "graphic/text/rendered_text.h"
 
 class FileSystem;
 class Image;
@@ -45,10 +47,10 @@
 	}
 
 	/*
-	 * Renders the given text into an image. The image is cached and therefore
+	 * Renders the given text into an set of images. The images are 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;
+	virtual const 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;

=== modified file 'src/graphic/graphic.h'
--- src/graphic/graphic.h	2017-01-25 18:55:59 +0000
+++ src/graphic/graphic.h	2017-05-11 13:11:27 +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/rendertarget.cc'
--- src/graphic/rendertarget.cc	2017-02-28 20:07:07 +0000
+++ src/graphic/rendertarget.cc	2017-05-11 13:11:27 +0000
@@ -143,11 +143,11 @@
  *
  * This blit function copies the pixels to the destination surface.
  */
-void RenderTarget::blit(const Vector2f& dst,
+void RenderTarget::blit(const Vector2i& dst,
                         const Image* image,
                         BlendMode blend_mode,
                         UI::Align align) {
-	Vector2f destination_point(dst);
+	Vector2i destination_point(dst);
 	UI::correct_for_align(align, image->width(), &destination_point);
 
 	Rectf source_rect(Vector2i(0, 0), image->width(), image->height());
@@ -160,11 +160,11 @@
 	}
 }
 
-void RenderTarget::blit_monochrome(const Vector2f& dst,
+void RenderTarget::blit_monochrome(const Vector2i& dst,
                                    const Image* image,
                                    const RGBAColor& blend_mode,
                                    UI::Align align) {
-	Vector2f destination_point(dst);
+	Vector2i destination_point(dst);
 	UI::correct_for_align(align, image->width(), &destination_point);
 
 	Rectf source_rect(Vector2i(0, 0), image->width(), image->height());

=== modified file 'src/graphic/rendertarget.h'
--- src/graphic/rendertarget.h	2017-02-23 19:38:51 +0000
+++ src/graphic/rendertarget.h	2017-05-11 13:11:27 +0000
@@ -62,13 +62,13 @@
 	void fill_rect(const Rectf&, const RGBAColor&, BlendMode blend_mode = BlendMode::Copy);
 	void brighten_rect(const Rectf&, int32_t factor);
 
-	void blit(const Vector2f& dst,
+	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 Vector2f& dst,
+	void blit_monochrome(const Vector2i& dst,
 	                     const Image* image,
 	                     const RGBAColor& blend_mode,
 	                     UI::Align = UI::Align::kLeft);

=== modified file 'src/graphic/richtext.cc'
--- src/graphic/richtext.cc	2017-02-23 19:38:51 +0000
+++ src/graphic/richtext.cc	2017-05-11 13:11:27 +0000
@@ -61,7 +61,7 @@
 	}
 
 	void draw(RenderTarget& dst) override {
-		dst.blit(Vector2f(0, 0), image);
+		dst.blit(Vector2i(), image);
 	}
 
 	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-11 13:11:27 +0000
@@ -14,6 +14,8 @@
     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

=== 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-11 13:11:27 +0000
@@ -0,0 +1,197 @@
+/*
+ * 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,
+                           const Image* init_image,
+                           bool visited,
+                           const RGBColor& color,
+                           bool is_background_color_set,
+                           DrawMode mode)
+   : rect_(init_rect),
+     image_(init_image),
+     visited_(visited),
+     background_color_(color),
+     is_background_color_set_(is_background_color_set),
+     mode_(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(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 {
+	return image_;
+}
+
+int RenderedRect::get_x() const {
+	return rect_.x;
+}
+
+int RenderedRect::get_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->get_x() + rect->width());
+	}
+	return result;
+}
+int RenderedText::height() const {
+	int result = 0;
+	for (const auto& rect : rects) {
+		result = std::max(result, rect->get_y() + rect->height());
+	}
+	return result;
+}
+
+void RenderedText::draw(RenderTarget& dst,
+                        const Vector2i& position,
+                        Recti region,
+                        Align align) const {
+
+	Vector2i aligned_pos(
+	   position.x - region.x, position.y - region.y);  // un-const the position and adjust for region
+	UI::correct_for_align(align, region.w, &aligned_pos);
+	for (const auto& rect : rects) {
+		Vector2i blit_point(aligned_pos.x + rect->get_x(), aligned_pos.y + rect->get_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(
+					   Rectf(tile_x, tile_y, tile_width, tile_height), rect->background_color());
+				}
+			}
+		}
+
+		if (rect->image()) {
+			switch (rect->mode()) {
+			// Draw a foreground texture
+			case RenderedRect::DrawMode::kBlit:
+				dst.blit(blit_point, rect->image());
+				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;
+			}
+		}
+		// TODO(GunChleoc): Remove this line when testing is done.
+		// dst.draw_rect(Rectf(blit_point.x, blit_point.y, rect->width(), rect->height()),
+		// RGBColor(100, 100, 100));
+	}
+}
+
+void RenderedText::draw(RenderTarget& dst, const Vector2i& position, UI::Align align) const {
+	draw(dst, position, Recti(0, 0, width(), height()), align);
+}
+
+// For testing purposes only. Needs to mirror the draw function.
+std::unique_ptr<Texture> RenderedText::as_texture() const {
+	std::unique_ptr<Texture> texture(new Texture(width(), height()));
+	for (const auto& rect : rects) {
+		const Rectf dest(rect->get_x(), rect->get_y(), rect->width(), rect->height());
+
+		// Draw Solid background Color
+		if (rect->has_background_color()) {
+			texture->fill_rect(dest, rect->background_color());
+		}
+
+		if (rect->image()) {
+			switch (rect->mode()) {
+			// Draw a foreground texture
+			case RenderedRect::DrawMode::kBlit:
+				texture->blit(dest, *rect->image(), dest, 1., BlendMode::Copy);
+				break;
+			// Draw a background image (tiling)
+			// TODO(GunChleoc): Support tiling here
+			case RenderedRect::DrawMode::kTile:
+				break;
+			}
+		}
+	}
+	return texture;
+}
+
+}  // 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-11 13:11:27 +0000
@@ -0,0 +1,124 @@
+/*
+ * 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:
+	RenderedRect(const Recti& init_rect,
+	             const Image* init_image,
+	             bool visited,
+	             const RGBColor& color,
+	             bool is_background_color_set,
+	             DrawMode 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
+	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 get_x() const;
+	/// The y position of the rectangle
+	int get_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 rather than an image
+	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_;
+	const Image* image_;  // Not owned
+	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;
+
+	/// Draw the rects. 'position', 'region' and 'align' are used to control the overall drawing
+	/// position and cropping
+	void draw(RenderTarget& dst,
+	          const Vector2i& position,
+	          Recti region,
+	          UI::Align align = UI::Align::kLeft) 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;
+
+	/// Blit everything into a single texture. Use this only for testing purposes.
+	std::unique_ptr<Texture> as_texture() const;
+};
+
+}  // namespace UI
+
+#endif  // end of include guard: WL_GRAPHIC_TEXT_RENDERED_TEXT_H

=== modified file 'src/graphic/text/rt_parse.cc'
--- src/graphic/text/rt_parse.cc	2017-02-24 08:37:28 +0000
+++ src/graphic/text/rt_parse.cc	2017-05-11 13:11:27 +0000
@@ -177,6 +177,8 @@
 Parser::Parser() {
 	{  // rt tag
 		TagConstraint tc;
+		// TODO(GunChleoc): padding on the bottom and on the right is broken.
+		// Maybe we don't need padding in the rt element anyway?
 		tc.allowed_attrs.insert("padding");
 		tc.allowed_attrs.insert("padding_r");
 		tc.allowed_attrs.insert("padding_l");

=== 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-11 13:11:27 +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,19 @@
      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 =
-	   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;
+UI::RenderedText* TextNode::render(TextureCache* texture_cache) {
+	const Image* img =
+	   &font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
+
+	UI::RenderedText* rendered_text = new UI::RenderedText();
+	rendered_text->rects.push_back(std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(img)));
+	return rendered_text;
 }
 
 /*
@@ -536,12 +570,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 +591,28 @@
 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();
+	const Image* rendered_image = texture_cache->get(hash);
+	if (!rendered_image) {
+		const Image& t =
+		   font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
+		std::unique_ptr<Texture> texture(new Texture(width(), height()));
+		for (uint16_t curx = 0; curx < w_; curx += t.width()) {
+			Rectf srcrect(0.f, 0.f, min<int>(t.width(), w_ - curx), h_);
+			texture->blit(Rectf(curx, 0, srcrect.w, srcrect.h), t, srcrect, 1., BlendMode::Copy);
+		}
+		rendered_image = texture_cache->insert(hash, std::move(texture));
 	}
-	return rv;
+
+	rendered_text->rects.push_back(
+	   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
+	return rendered_text;
 }
 
 /*
@@ -569,20 +622,35 @@
 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();
+			const Image* rendered_image = texture_cache->get(hash);
+			if (!rendered_image) {
+				std::unique_ptr<Texture> texture(new Texture(width(), height()));
+				texture->fill_rect(Rectf(0, 0, w_, h_), RGBAColor(0xcc, 0, 0, 0xcc));
+				rendered_image = texture_cache->insert(hash, std::move(texture));
+			}
+
+			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 +667,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 +695,77 @@
 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_);
-
-		// 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);
+	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();
+		const Image* rendered_image = texture_cache->get(hash);
+		if (!rendered_image) {
+			std::unique_ptr<Texture> texture(new Texture(width(), height()));
+
+			// 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_;
+					texture->blit(dst, *background_image_, srcrect, 1., BlendMode::Copy);
+				}
+			} else {
+				texture->fill_rect(Rectf(0, 0, 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;
+
+		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,44 @@
 		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();
 
 		// 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);
+			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 (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);
+		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() + n->x() + margin_.left, y() + n->y() + margin_.top));
+				} else {
+					rendered_rect->set_origin(Vector2i(
+					   x() + rendered_rect->get_x(), y() + rendered_rect->get_y() + margin_.top));
 				}
-			}
-			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_rect->set_visited();
+				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 +892,48 @@
 	   : RenderNode(ns),
 	     image_(use_playercolor ? playercolor_image(color, image_filename) :
 	                              g_gr->images().get(image_filename)),
+	     filename_(image_filename),
 	     scale_(scale) {
-	}
-
-	uint16_t width() override {
+		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_;
 };
 
-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();
+
+	const std::string hash =
+	   (boost::format("rt:img:%s:%i:%i") % filename_ % width() % height()).str();
+	const Image* rendered_image = texture_cache->get(hash);
+	if (!rendered_image) {
+		std::unique_ptr<Texture> texture(new Texture(width(), height()));
+		texture->blit(Rectf(0, 0, width(), height()), *image_,
+		              Rectf(0, 0, image_->width(), image_->height()), 1., BlendMode::Copy);
+		rendered_image = texture_cache->insert(hash, std::move(texture));
+	}
+
+	rendered_text->rects.push_back(
+	   std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
+	return rendered_text;
 }
 // End: Helper Stuff
 
@@ -1136,6 +1240,7 @@
 	                 const UI::FontSets& fontsets)
 	   : TagHandler(tag, fc, ns, image_cache, init_renderer_style, fontsets),
 	     background_image_(nullptr),
+	     image_filename_(""),
 	     space_(0) {
 	}
 
@@ -1151,6 +1256,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 +1278,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 +1287,7 @@
 private:
 	string fill_text_;
 	const Image* background_image_;
+	std::string image_filename_;
 	uint16_t space_;
 };
 
@@ -1481,7 +1588,7 @@
 	return nodes[0];
 }
 
-Texture* Renderer::render(const string& text, uint16_t width, const TagSet& allowed_tags) {
+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_);

=== 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-11 13:11:27 +0000
@@ -29,6 +29,7 @@
 #include "graphic/color.h"
 #include "graphic/image.h"
 #include "graphic/text/font_set.h"
+#include "graphic/text/rendered_text.h"
 
 class Texture;
 class ImageCache;
@@ -82,7 +83,7 @@
 	// 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());
+	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-11 13:11:27 +0000
@@ -63,15 +63,15 @@
 	*gh = h;
 }
 
-const Texture& SdlTtfFont::render(const std::string& txt,
-                                  const RGBColor& clr,
-                                  int style,
-                                  TextureCache* texture_cache) {
+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);
+	const Image* rv = texture_cache->get(hash);
 	if (rv)
 		return *rv;
 

=== 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-11 13:11:27 +0000
@@ -51,7 +51,7 @@
 	}
 
 	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 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 +64,7 @@
 	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;
+	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/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-11 13:11:27 +0000
@@ -138,12 +138,13 @@
 	StandaloneRenderer standalone_renderer;
 
 	try {
-		std::unique_ptr<Texture> texture(
+		std::unique_ptr<UI::RenderedText> rendered_text(
 		   standalone_renderer.renderer()->render(txt, w, allowed_tags));
 
 		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)) {
+
+		if (!save_to_png(rendered_text->as_texture().get(), sw.get(), ColorType::RGBA)) {
 			std::cout << "Could not encode PNG." << std::endl;
 		}
 	} catch (RT::Exception& e) {

=== modified file 'src/graphic/text_layout.cc'
--- src/graphic/text_layout.cc	2017-05-03 07:24:06 +0000
+++ src/graphic/text_layout.cc	2017-05-11 13:11:27 +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();
 }
 
@@ -69,9 +70,7 @@
 
 std::string as_game_tip(const std::string& txt) {
 	static boost::format f(
-	   "<rt padding_l=48 padding_t=28 padding_r=48 padding_b=28>"
-	   "<p align=center><font color=21211b face=serif size=16>%s</font></p></rt>");
-
+	   "<rt><p align=center><font color=21211b face=serif size=16>%s</font></p></rt>");
 	f % txt;
 	return f.str();
 }
@@ -162,8 +161,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));
+const UI::RenderedText*
+autofit_ui_text(const std::string& text, int width, RGBColor color, int fontsize) {
+	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(
@@ -204,7 +205,7 @@
  * subsampled rendering - this can lead to blurry texts. That is why we
  * never do float divisions in this function.
  */
-void correct_for_align(Align align, uint32_t w, Vector2f* pt) {
+void correct_for_align(Align align, uint32_t w, Vector2i* pt) {
 
 	if (align == Align::kCenter)
 		pt->x -= w / 2;
@@ -215,7 +216,7 @@
 /**
  * Adjust the y coordinate in 'point 'pt' to vertically center an element with height 'h'.
  */
-void center_vertically(uint32_t h, Vector2f* pt) {
+void center_vertically(uint32_t h, Vector2i* pt) {
 	pt->y -= h / 2;
 }
 }  // namespace UI

=== modified file 'src/graphic/text_layout.h'
--- src/graphic/text_layout.h	2017-03-29 12:36:20 +0000
+++ src/graphic/text_layout.h	2017-05-11 13:11:27 +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,17 +95,17 @@
   * 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);
+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);
 
-void center_vertically(uint32_t h, Vector2f* pt);
-void correct_for_align(Align, uint32_t w, Vector2f* pt);
+void center_vertically(uint32_t h, Vector2i* pt);
+void correct_for_align(Align, uint32_t w, Vector2i* pt);
 
 }  // namespace UI
 

=== modified file 'src/graphic/texture_cache.cc'
--- src/graphic/texture_cache.cc	2017-01-25 18:55:59 +0000
+++ src/graphic/texture_cache.cc	2017-05-11 13:11:27 +0000
@@ -24,7 +24,7 @@
 #include <SDL.h>
 #include <stdint.h>
 
-#include "graphic/texture.h"
+#include "graphic/image.h"
 
 // The implementation took inspiration from
 // http://timday.bitbucket.org/lru.html, but our use case here is a little
@@ -44,7 +44,7 @@
 	size_in_bytes_ = 0;
 }
 
-Texture* TextureCache::get(const std::string& hash) {
+const Image* TextureCache::get(const std::string& hash) {
 	const auto it = entries_.find(hash);
 	if (it == entries_.end())
 		return nullptr;
@@ -56,7 +56,7 @@
 	return it->second.texture.get();
 }
 
-Texture* TextureCache::insert(const std::string& hash, std::unique_ptr<Texture> texture) {
+const Image* TextureCache::insert(const std::string& hash, std::unique_ptr<const Image> texture) {
 	assert(entries_.find(hash) == entries_.end());
 
 	const uint32_t texture_size = texture->width() * texture->height() * 4;

=== modified file 'src/graphic/texture_cache.h'
--- src/graphic/texture_cache.h	2017-01-25 18:55:59 +0000
+++ src/graphic/texture_cache.h	2017-05-11 13:11:27 +0000
@@ -29,8 +29,9 @@
 #include <boost/utility.hpp>
 
 #include "base/macros.h"
+#include "graphic/text/rendered_text.h"
 
-class Texture;
+class Image;
 
 // 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
@@ -50,21 +51,21 @@
 	void flush();
 
 	/// Returns an entry if it is cached, nullptr otherwise.
-	Texture* get(const std::string& hash);
+	const Image* 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);
+	const Image* insert(const std::string& hash, std::unique_ptr<const Image> texture);
 
 private:
 	void drop();
 
 	using AccessHistory = std::list<std::string>;
 	struct Entry {
-		std::unique_ptr<Texture> texture;
+		std::unique_ptr<const Image> texture;
 		uint32_t last_access;  // Mainly for debugging and analysis.
 		const AccessHistory::iterator list_iterator;
 	};

=== modified file 'src/graphic/wordwrap.cc'
--- src/graphic/wordwrap.cc	2017-04-30 07:44:27 +0000
+++ src/graphic/wordwrap.cc	2017-05-11 13:11:27 +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,21 +301,21 @@
 
 	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;
 
-		Vector2f point(where.x, where.y);
+		Vector2i point(where.x, where.y);
 
 		if (alignment == UI::Align::kRight) {
 			point.x += wrapwidth_ - kLineMargin;
 		}
 
-		const Image* entry_text_im = UI::g_fh1->render(
+		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);
@@ -327,9 +323,9 @@
 			int caret_x = text_width(line_to_caret, fontsize_);
 
 			const Image* caret_image = g_gr->images().get("images/ui_basic/caret.png");
-			Vector2f caretpt;
+			Vector2i caretpt;
 			caretpt.x = point.x + caret_x - caret_image->width() + kLineMargin;
-			caretpt.y = point.y + (fontheight - caret_image->height()) / 2.f;
+			caretpt.y = point.y + (fontheight - caret_image->height()) / 2;
 			dst.blit(caretpt, caret_image);
 		}
 	}

=== modified file 'src/graphic/wordwrap.h'
--- src/graphic/wordwrap.h	2017-04-29 14:57:30 +0000
+++ src/graphic/wordwrap.h	2017-05-11 13:11:27 +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-04-30 08:27:40 +0000
+++ src/logic/map_objects/map_object.cc	2017-05-11 13:11:27 +0000
@@ -470,22 +470,20 @@
 	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 =
+	const UI::RenderedText* rendered_census =
 	   UI::g_fh1->render(as_condensed(census, UI::Align::kCenter, font_size), 120);
-
-	// Rounding guarantees that text aligns with pixels to avoid subsampling.
-	const Vector2f census_pos = round(field_on_dst - Vector2f(0, 48) * scale).cast<float>();
+	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, UI::Align::kCenter);
+		UI::correct_for_align(UI::Align::kCenter, rendered_census->width(), &position);
+		rendered_census->draw(*dst, position);
 	}
 
 	if (draw_text & TextToDraw::kStatistics && !statictics.empty()) {
-		const Vector2f statistics_pos =
-		   round(census_pos + Vector2f(0, rendered_census_info->height() / 2.f + 10 * scale))
-		      .cast<float>();
-		dst->blit(statistics_pos,
-		          UI::g_fh1->render(as_condensed(statictics, UI::Align::kCenter, font_size)),
-		          BlendMode::UseAlpha, UI::Align::kCenter);
+		const UI::RenderedText* rendered_statistics =
+		   UI::g_fh1->render(as_condensed(statictics, UI::Align::kCenter, font_size));
+		position +=
+		   Vector2i(rendered_census->width() / 2, rendered_census->height() / 2 + 10 * scale);
+		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-11 13:11:27 +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-02-27 22:09:57 +0000
+++ src/ui_basic/button.cc	2017-05-11 13:11:27 +0000
@@ -59,9 +59,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);
 	}
@@ -173,13 +171,13 @@
 	if (pic_custom_) {
 		if (image_mode_ == UI::Button::ImageMode::kUnscaled) {
 			if (!is_monochrome) {
-				dst.blit(Vector2f((get_w() - static_cast<int32_t>(pic_custom_->width())) / 2.f,
-				                  (get_h() - static_cast<int32_t>(pic_custom_->height())) / 2.f),
+				dst.blit(Vector2i((get_w() - static_cast<int32_t>(pic_custom_->width())) / 2,
+				                  (get_h() - static_cast<int32_t>(pic_custom_->height())) / 2),
 				         pic_custom_);
 			} else {
 				dst.blit_monochrome(
-				   Vector2f((get_w() - static_cast<int32_t>(pic_custom_->width())) / 2.f,
-				            (get_h() - static_cast<int32_t>(pic_custom_->height())) / 2.f),
+				   Vector2i((get_w() - static_cast<int32_t>(pic_custom_->width())) / 2,
+				            (get_h() - static_cast<int32_t>(pic_custom_->height())) / 2),
 				   pic_custom_, RGBAColor(255, 255, 255, 127));
 			}
 		} else {
@@ -208,13 +206,13 @@
 
 	} else if (title_.length()) {
 		//  Otherwise draw title string centered
-		const Image* entry_text_im =
+		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(
-		   Vector2f((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),
+		                    Recti(0, 0, rendered_text->width(), rendered_text->height()));
 	}
 
 	//  draw border

=== modified file 'src/ui_basic/checkbox.cc'
--- src/ui_basic/checkbox.cc	2017-02-23 17:58:25 +0000
+++ src/ui_basic/checkbox.cc	2017-05-11 13:11:27 +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();
 }
@@ -134,7 +134,7 @@
 		const uint16_t w = pic_graphics_->width();
 		const uint16_t h = pic_graphics_->height();
 
-		dst.blit(Vector2f((get_inner_w() - w) / 2, (get_inner_h() - h) / 2), pic_graphics_);
+		dst.blit(Vector2i((get_inner_w() - w) / 2, (get_inner_h() - h) / 2), pic_graphics_);
 
 		if (flags_ & Is_Checked) {
 			dst.draw_rect(Rectf(0.f, 0.f, get_w(), get_h()), RGBColor(229, 116, 2));
@@ -145,7 +145,7 @@
 		static_assert(0 <= kStateboxSize, "assert(0 <= STATEBOX_WIDTH) failed.");
 		static_assert(0 <= kStateboxSize, "assert(0 <= STATEBOX_HEIGHT) failed.");
 		Vector2f image_anchor(0.f, 0.f);
-		Vector2f text_anchor(kStateboxSize + kPadding, 0);
+		Vector2i text_anchor(kStateboxSize + kPadding, 0);
 
 		if (rendered_text_) {
 			if (UI::g_fh1->fontset()->is_rtl()) {
@@ -153,7 +153,8 @@
 				image_anchor.x = rendered_text_->width() + kPadding;
 				image_anchor.y = (get_h() - kStateboxSize) / 2;
 			}
-			dst.blit(text_anchor, rendered_text_, BlendMode::UseAlpha, UI::Align::kLeft);
+			rendered_text_->draw(
+			   dst, text_anchor, Recti(0, 0, rendered_text_->width(), rendered_text_->height()));
 		}
 
 		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-11 13:11:27 +0000
@@ -90,8 +90,8 @@
 			flags_ |= flags;
 	}
 	const Image* pic_graphics_;
+	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-02-28 20:07:07 +0000
+++ src/ui_basic/editbox.cc	2017-05-11 13:11:27 +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,17 +372,12 @@
 
 	const int max_width = get_w() - 2 * kMarginX;
 
-	const Image* entry_text_im = 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();
-
-	Vector2f point(kMarginX, get_h() / 2);
+	const UI::RenderedText* rendered_text = UI::g_fh1->render(as_editorfont(m_->text, m_->fontsize));
+
+	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) {
 		point.x += max_width - linewidth;
 	}
@@ -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,12 +412,12 @@
 		// 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");
-		Vector2f caretpt;
+		Vector2i caretpt;
 		caretpt.x = point.x + m_->scrolloffset + caret_x - caret_image->width() + kLineMargin;
-		caretpt.y = point.y + (fontheight - caret_image->height()) / 2.f;
+		caretpt.y = point.y + (fontheight - caret_image->height()) / 2;
 		dst.blit(caretpt, caret_image);
 	}
 }

=== modified file 'src/ui_basic/fullscreen_window.cc'
--- src/ui_basic/fullscreen_window.cc	2017-02-23 19:38:51 +0000
+++ src/ui_basic/fullscreen_window.cc	2017-05-11 13:11:27 +0000
@@ -150,7 +150,7 @@
 			const int h = (tiling == kHorizontal) ? image->height() : get_h();
 			dst.tile(Recti(x, y, w, h), image, Vector2i(0, 0));
 		} else {
-			dst.blit(Vector2f(x, y), image);
+			dst.blit(Vector2i(x, y), image);
 		}
 	}
 }

=== modified file 'src/ui_basic/listselect.cc'
--- src/ui_basic/listselect.cc	2017-03-06 06:57:47 +0000
+++ src/ui_basic/listselect.cc	2017-05-11 13:11:27 +0000
@@ -54,9 +54,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()),
@@ -334,10 +332,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(
+			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());
 			}
@@ -379,10 +377,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(
+		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);
@@ -390,7 +388,7 @@
 			break;
 		}
 
-		Vector2f point(selection_mode_ == ListselectLayout::kDropdown ? 3.f : 1.f, y);
+		Vector2i point(selection_mode_ == ListselectLayout::kDropdown ? 3 : 1, y);
 		uint32_t maxw =
 		   get_eff_w() -
 		   (selection_mode_ == ListselectLayout::kDropdown ? scrollbar_.is_enabled() ? 4 : 5 : 2);
@@ -417,7 +415,7 @@
 
 		// Now draw pictures
 		if (er.pic) {
-			dst.blit(Vector2f(UI::g_fh1->fontset()->is_rtl() ? get_eff_w() - er.pic->width() - 1 : 1,
+			dst.blit(Vector2i(UI::g_fh1->fontset()->is_rtl() ? get_eff_w() - er.pic->width() - 1 : 1,
 			                  y + (get_lineheight() - er.pic->height()) / 2),
 			         er.pic);
 		}
@@ -427,7 +425,7 @@
 			point.x += maxw - picw;
 		}
 
-		UI::correct_for_align(alignment, entry_text_im->width(), &point);
+		UI::correct_for_align(alignment, rendered_text->width(), &point);
 
 		// Shift for image width
 		if (!UI::g_fh1->fontset()->is_rtl()) {
@@ -435,10 +433,10 @@
 		}
 
 		// 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
@@ -449,16 +447,16 @@
 
 		// Crop to column width while blitting
 		if ((alignment == UI::Align::kRight) &&
-		    (maxw + picw) < static_cast<uint32_t>(entry_text_im->width())) {
+		    (maxw + picw) < static_cast<uint32_t>(rendered_text->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()));
+			rendered_text->draw(dst, point, Recti(rendered_text->width() - maxw + picw, 0, maxw,
+			                                      rendered_text->height()));
 		} else {
-			dst.blitrect(point, entry_text_im, Recti(0, 0, maxw, lineheight));
+			rendered_text->draw(dst, point, Recti(0, 0, maxw, lineheight));
 		}
 
 		y += get_lineheight();

=== 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-11 13:11:27 +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);
+		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);
+		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-03-04 18:02:23 +0000
+++ src/ui_basic/multilineeditbox.cc	2017-05-11 13:11:27 +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-03-04 18:02:23 +0000
+++ src/ui_basic/multilinetextarea.cc	2017-05-11 13:11:27 +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,15 +144,10 @@
 	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()));
+		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(), static_cast<int>(get_inner_h()));
 
 		if (blit_width > 0 && blit_height > 0) {
 			int anchor = 0;
@@ -173,10 +162,8 @@
 			case UI::Align::kLeft:
 				anchor = RICHTEXT_MARGIN;
 			}
-
-			dst.blitrect(Vector2f(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-02-27 13:53:04 +0000
+++ src/ui_basic/panel.cc	2017-05-11 13:11:27 +0000
@@ -191,7 +191,7 @@
 			RenderTarget& rt = *g_gr->get_render_target();
 			forefather->do_draw(rt);
 			rt.blit(
-			   (app->get_mouse_position() - Vector2i(3, 7)).cast<float>(),
+			   (app->get_mouse_position() - Vector2i(3, 7)),
 			   WLApplication::get()->is_mouse_pressed() ? default_cursor_click_ : default_cursor_);
 			forefather->do_tooltip();
 			g_gr->refresh();
@@ -1060,25 +1060,23 @@
 		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) {
-		return false;
-	}
-	uint16_t tip_width = rendered_text->width() + 4;
-	uint16_t tip_height = rendered_text->height() + 4;
-
-	Rectf r(WLApplication::get()->get_mouse_position() + Vector2i(2, 32), tip_width, tip_height);
-	const Vector2f tooltip_bottom_right = r.opposite_of_origin();
-	const Vector2f screen_bottom_right(g_gr->get_xres(), g_gr->get_yres());
+	constexpr uint32_t kTipWidthMax = 360;
+	const UI::RenderedText* rendered_text = g_fh1->render(text_to_render, kTipWidthMax);
+
+	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();
+	const Vector2i screen_bottom_right(g_gr->get_xres(), g_gr->get_yres());
 	if (screen_bottom_right.x < tooltip_bottom_right.x)
 		r.x -= 4 + r.w;
 	if (screen_bottom_right.y < tooltip_bottom_right.y)
 		r.y -= 35 + r.h;
 
-	dst.fill_rect(r, RGBColor(63, 52, 34));
-	dst.draw_rect(r, RGBColor(0, 0, 0));
-	dst.blit(r.origin() + Vector2f(2.f, 2.f), rendered_text);
+	dst.fill_rect(r.cast<float>(), RGBColor(63, 52, 34));
+	dst.draw_rect(r.cast<float>(), RGBColor(0, 0, 0));
+	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-11 13:11:27 +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-02-26 11:00:07 +0000
+++ src/ui_basic/progressbar.cc	2017-05-11 13:11:27 +0000
@@ -88,9 +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));
-	Vector2f pos(get_w() / 2, get_h() / 2);
+	const UI::RenderedText* rendered_text = UI::g_fh1->render(as_uifont(progress_text));
+	Vector2i pos(get_w() / 2, get_h() / 2);
 	UI::center_vertically(rendered_text->height(), &pos);
-	dst.blit(pos, rendered_text, BlendMode::UseAlpha, UI::Align::kCenter);
+	rendered_text->draw(dst, pos, UI::Align::kCenter);
 }
 }

=== modified file 'src/ui_basic/progresswindow.cc'
--- src/ui_basic/progresswindow.cc	2017-02-24 10:21:37 +0000
+++ src/ui_basic/progresswindow.cc	2017-05-11 13:11:27 +0000
@@ -62,8 +62,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;
@@ -98,10 +97,10 @@
 	draw(rt);
 
 	rt.fill_rect(label_rectangle_, PROGRESS_FONT_COLOR_BG);
-	const Image* rendered_text =
+	const UI::RenderedText* rendered_text =
 	   UI::g_fh1->render(as_uifont(description, UI_FONT_SIZE_SMALL, PROGRESS_FONT_COLOR_FG));
 	UI::center_vertically(rendered_text->height(), &label_center_);
-	rt.blit(label_center_, rendered_text, BlendMode::UseAlpha, UI::Align::kCenter);
+	rendered_text->draw(rt, label_center_, UI::Align::kCenter);
 
 #ifdef _WIN32
 	// Pump events to prevent "not responding" on windows

=== modified file 'src/ui_basic/progresswindow.h'
--- src/ui_basic/progresswindow.h	2017-02-24 10:21:37 +0000
+++ src/ui_basic/progresswindow.h	2017-05-11 13:11:27 +0000
@@ -63,7 +63,7 @@
 private:
 	using VisualizationArray = std::vector<IProgressVisualization*>;
 
-	Vector2f label_center_;
+	Vector2i label_center_;
 	Rectf label_rectangle_;
 	VisualizationArray visualizations_;
 	std::string background_;

=== modified file 'src/ui_basic/slider.cc'
--- src/ui_basic/slider.cc	2017-02-24 19:22:36 +0000
+++ src/ui_basic/slider.cc	2017-05-11 13:11:27 +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,10 +533,10 @@
 	uint32_t gap_n = get_w() / labels.size();
 
 	for (uint32_t i = 0; i < labels.size(); i++) {
-		const Image* rendered_text =
+		const UI::RenderedText* rendered_text =
 		   UI::g_fh1->render(as_condensed(labels[i], UI::Align::kCenter, UI_FONT_SIZE_SMALL - 2));
-		dst.blit(Vector2f(gap_1 + i * gap_n, get_h() - rendered_text->height()), rendered_text,
-		         BlendMode::UseAlpha, UI::Align::kCenter);
+		rendered_text->draw(
+		   dst, Vector2i(gap_1 + i * gap_n, get_h() - rendered_text->height()), UI::Align::kCenter);
 	}
 }
 
@@ -556,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-04-25 08:10:11 +0000
+++ src/ui_basic/table.cc	2017-05-11 13:11:27 +0000
@@ -52,11 +52,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_(
@@ -242,14 +239,14 @@
 			const Image* entry_picture = er.get_picture(i);
 			const std::string& entry_string = er.get_string(i);
 
-			Vector2f point(curx, y);
+			Vector2i point(curx, y);
 			int picw = 0;
 
 			if (entry_picture != nullptr) {
 				picw = entry_picture->width();
 				const int pich = entry_picture->height();
 
-				float draw_x = point.x;
+				int draw_x = point.x;
 
 				// We want a bit of margin
 				int max_pic_height = lineheight - 3;
@@ -263,7 +260,7 @@
 						if (i == nr_columns - 1 && scrollbar_->is_enabled()) {
 							draw_x = point.x + (curw - blit_width - scrollbar_->get_w()) / 2.f;
 						} else {
-							draw_x = point.x + (curw - blit_width) / 2.f;
+							draw_x = point.x + (curw - blit_width) / 2;
 						}
 					}
 
@@ -272,7 +269,7 @@
 					}
 
 					// Create the scaled image
-					dst.blitrect_scale(Rectf(draw_x, point.y + 1.f, blit_width, max_pic_height),
+					dst.blitrect_scale(Rectf(draw_x, point.y + 1, blit_width, max_pic_height),
 					                   entry_picture, Recti(0, 0, picw, pich), 1., BlendMode::UseAlpha);
 
 					// For text alignment below
@@ -282,12 +279,12 @@
 						if (i == nr_columns - 1 && scrollbar_->is_enabled()) {
 							draw_x = point.x + (curw - picw - scrollbar_->get_w()) / 2.f;
 						} else {
-							draw_x = point.x + (curw - picw) / 2.f;
+							draw_x = point.x + (curw - picw) / 2;
 						}
 					} else if (alignment == UI::Align::kRight) {
 						draw_x += curw - picw;
 					}
-					dst.blit(Vector2f(draw_x, point.y + (lineheight - pich) / 2.f), entry_picture);
+					dst.blit(Vector2i(draw_x, point.y + (lineheight - pich) / 2), entry_picture);
 				}
 				point.x += picw;
 			}
@@ -298,7 +295,8 @@
 				curx += curw;
 				continue;
 			}
-			const Image* entry_text_im = UI::g_fh1->render(as_uifont(richtext_escape(entry_string)));
+			const UI::RenderedText* rendered_text =
+			   UI::g_fh1->render(as_uifont(richtext_escape(entry_string)));
 
 			switch (alignment) {
 			case UI::Align::kCenter:
@@ -312,7 +310,7 @@
 			}
 
 			// Add an offset for rightmost column when the scrollbar is shown.
-			int text_width = entry_text_im->width();
+			int text_width = rendered_text->width();
 			if (i == nr_columns - 1 && scrollbar_->is_enabled()) {
 				text_width = text_width + scrollbar_->get_w();
 			}
@@ -325,15 +323,15 @@
 					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));
+				// Restrict check for efficiency
+				if (i18n::has_rtl_character(entry_string.c_str(), 20)) {
+					rendered_text->draw(
+					   dst, point, Recti(text_width - curw + picw, 0, text_width, lineheight));
 				} else {
-					dst.blitrect(point, entry_text_im, Recti(0, 0, curw - picw, lineheight));
+					rendered_text->draw(dst, point, Recti(0, 0, curw - picw, lineheight));
 				}
 			} else {
-				dst.blitrect(point, entry_text_im, Recti(0, 0, curw - picw, lineheight));
+				rendered_text->draw(dst, point, Recti(0, 0, curw - picw, lineheight));
 			}
 			curx += curw;
 		}

=== modified file 'src/ui_basic/tabpanel.cc'
--- src/ui_basic/tabpanel.cc	2017-03-04 09:14:37 +0000
+++ src/ui_basic/tabpanel.cc	2017-05-11 13:11:27 +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(Rectf(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(Vector2f(x + kTabPanelTextMargin,
-			                  (kTabPanelButtonHeight - tabs_[idx]->pic->height()) / 2),
-			         tabs_[idx]->pic, BlendMode::UseAlpha, UI::Align::kLeft);
+		} 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-11 13:11:27 +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;
+	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-02-27 13:48:29 +0000
+++ src/ui_basic/textarea.cc	2017-05-11 13:11:27 +0000
@@ -126,9 +126,9 @@
 void Textarea::draw(RenderTarget& dst) {
 	if (!text_.empty()) {
 		// Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
-		Vector2f anchor(
+		Vector2i anchor(
 		   (align_ == Align::kCenter) ? get_w() / 2 : (align_ == UI::Align::kRight) ? get_w() : 0, 0);
-		dst.blit(anchor, rendered_text_, BlendMode::UseAlpha, align_);
+		rendered_text_->draw(dst, anchor, align_);
 	}
 }
 
@@ -193,9 +193,7 @@
 		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-11 13:11:27 +0000
@@ -94,7 +94,7 @@
 
 	LayoutMode layoutmode_;
 	std::string text_;
-	const Image* rendered_text_;
+	const UI::RenderedText* rendered_text_;
 	Align align_;
 	RGBColor color_;
 	int fontsize_;

=== modified file 'src/ui_basic/window.cc'
--- src/ui_basic/window.cc	2017-02-27 13:48:29 +0000
+++ src/ui_basic/window.cc	2017-05-11 13:11:27 +0000
@@ -272,13 +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 =
+		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.
-		Vector2f pos(get_lborder() + get_inner_w() / 2, TP_B_PIXMAP_THICKNESS / 2);
+		Vector2i pos(get_lborder() + get_inner_w() / 2, TP_B_PIXMAP_THICKNESS / 2);
 		UI::center_vertically(text->height(), &pos);
-		dst.blit(pos, text, BlendMode::UseAlpha, UI::Align::kCenter);
+		text->draw(dst, pos, UI::Align::kCenter);
 	}
 
 	if (!is_minimal_) {

=== modified file 'src/wui/chatoverlay.cc'
--- src/wui/chatoverlay.cc	2017-01-25 18:55:59 +0000
+++ src/wui/chatoverlay.cc	2017-05-11 13:11:27 +0000
@@ -167,13 +167,13 @@
 	if (!m->havemessages_)
 		return;
 
-	const Image* im = nullptr;
+	const UI::RenderedText* im = 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);
 
@@ -185,8 +185,6 @@
 	if (!m->transparent_) {
 		dst.fill_rect(Rectf(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(Vector2f(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-01-25 18:55:59 +0000
+++ src/wui/game_tips.cc	2017-05-11 13:11:27 +0000
@@ -99,19 +99,21 @@
 }
 
 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();
-	Vector2f pt((g_gr->get_xres() - w) / 2, (g_gr->get_yres() - h) / 2);
-	Rectf tips_area(pt, w, h);
+	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);
 	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() - Vector2f(rendered_text->width() / 2, rendered_text->height() / 2),
-	        rendered_text);
+	constexpr int kPaddingW = 48;
+	constexpr int kPaddingH = 28;
+
+	Recti tips_area(pt.x + kPaddingW, pt.y + kPaddingH, w - 2 * kPaddingW, h - 2 * kPaddingH);
+	const UI::RenderedText* rendered_text =
+	   UI::g_fh1->render(as_game_tip(tips_[index].text), tips_area.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/inputqueuedisplay.cc'
--- src/wui/inputqueuedisplay.cc	2017-01-25 18:55:59 +0000
+++ src/wui/inputqueuedisplay.cc	2017-05-11 13:11:27 +0000
@@ -131,7 +131,7 @@
 	uint32_t nr_inputs_to_draw = std::min(queue_->get_filled(), cache_size_);
 	uint32_t nr_empty_to_draw = cache_size_ - nr_inputs_to_draw;
 
-	Vector2f point;
+	Vector2i point;
 	point.x = Border + (show_only_ ? 0 : CellWidth + CellSpacing);
 	point.y = Border + (total_height_ - 2 * Border - WARE_MENU_PIC_HEIGHT) / 2;
 

=== modified file 'src/wui/interactive_base.cc'
--- src/wui/interactive_base.cc	2017-03-03 18:13:55 +0000
+++ src/wui/interactive_base.cc	2017-05-11 13:11:27 +0000
@@ -357,9 +357,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(Vector2f(5, 5), UI::g_fh1->render(gametime_text), BlendMode::UseAlpha,
-			         UI::Align::kLeft);
+			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());
@@ -368,20 +367,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);
-
-		dst.blit(Vector2f(get_w() - 5, get_h() - rendered_text->height() - 5), rendered_text,
-		         BlendMode::UseAlpha, UI::Align::kRight);
+		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(
+		const UI::RenderedText* rendered_text = UI::g_fh1->render(as_condensed(
 		   (fps_format % (1000.0 / frametime_) % (1000.0 / (avg_usframetime_ / 1000))).str()));
-		dst.blit(Vector2f((get_w() - rendered_text->width()) / 2, 5), rendered_text,
-		         BlendMode::UseAlpha, UI::Align::kLeft);
+		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-11 13:11:27 +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-02-28 16:00:20 +0000
+++ src/wui/interactive_gamebase.cc	2017-05-11 13:11:27 +0000
@@ -126,8 +126,8 @@
 		}
 
 		if (!game_speed.empty()) {
-			dst.blit(Vector2f(get_w() - 5, 5), UI::g_fh1->render(game_speed), BlendMode::UseAlpha,
-			         UI::Align::kRight);
+			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/itemwaresdisplay.cc'
--- src/wui/itemwaresdisplay.cc	2017-01-25 18:55:59 +0000
+++ src/wui/itemwaresdisplay.cc	2017-05-11 13:11:27 +0000
@@ -117,7 +117,7 @@
 		} else {
 			y += IWD_WareBaseLine;
 			if (tribe.get_ware_descr(it.index)->icon())
-				dst.blit(Vector2f(x, y), tribe.get_ware_descr(it.index)->icon());
+				dst.blit(Vector2i(x, y), tribe.get_ware_descr(it.index)->icon());
 		}
 	}
 }

=== modified file 'src/wui/mapdetails.cc'
--- src/wui/mapdetails.cc	2017-02-23 17:58:25 +0000
+++ src/wui/mapdetails.cc	2017-05-11 13:11:27 +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/minimap.cc'
--- src/wui/minimap.cc	2017-02-12 09:10:57 +0000
+++ src/wui/minimap.cc	2017-05-11 13:11:27 +0000
@@ -52,7 +52,7 @@
 void MiniMap::View::draw(RenderTarget& dst) {
 	minimap_image_ = draw_minimap(ibase_.egbase(), ibase_.get_player(), view_area_, *minimap_type_,
 	                              *minimap_layers_ | MiniMapLayer::ViewWindow);
-	dst.blit(Vector2f(), minimap_image_.get());
+	dst.blit(Vector2i(), minimap_image_.get());
 }
 
 /*

=== modified file 'src/wui/plot_area.cc'
--- src/wui/plot_area.cc	2017-05-02 12:09:44 +0000
+++ src/wui/plot_area.cc	2017-05-11 13:11:27 +0000
@@ -176,18 +176,17 @@
  */
 void draw_value(const string& value,
                 const RGBColor& color,
-                const Vector2f& pos,
+                const Vector2i& pos,
                 RenderTarget& dst) {
-	const Image* pic = UI::g_fh1->render(ytick_text_style(value, color));
-	Vector2f point(pos);  // Un-const this
-	UI::center_vertically(pic->height(), &point);
-	dst.blit(point, pic, BlendMode::UseAlpha, UI::Align::kRight);
+	const UI::RenderedText* tick = UI::g_fh1->render(ytick_text_style(value, color));
+	Vector2i point(pos);  // Un-const this
+	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) {
@@ -262,11 +261,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(
+		const UI::RenderedText* xtick = UI::g_fh1->render(
 		   xtick_text_style((boost::format("-%u ") % (max_x / how_many_ticks * i)).str()));
-		Vector2f pos(posx, inner_h - kSpaceBottom + 10);
+		Vector2i pos(posx, inner_h - kSpaceBottom + 10);
 		UI::center_vertically(xtick->height(), &pos);
-		dst.blit(pos, xtick, BlendMode::UseAlpha, UI::Align::kCenter);
+		xtick->draw(dst, pos, UI::Align::kCenter);
 
 		posx -= sub;
 	}
@@ -281,10 +280,10 @@
 	   kAxisLineColor, kAxisLinesWidth);
 
 	//  print the used unit
-	const Image* xtick = UI::g_fh1->render(xtick_text_style(get_generic_unit_name(unit)));
-	Vector2f pos(2, kSpacing + 2);
+	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, UI::Align::kLeft);
+	xtick->draw(dst, pos);
 }
 
 }  // namespace
@@ -482,7 +481,7 @@
 
 	//  print the maximal value into the top right corner
 	draw_value(yscale_label, RGBColor(60, 125, 0),
-	           Vector2f(get_inner_w() - kSpaceRight - 3, kSpacing + 2), dst);
+	           Vector2i(get_inner_w() - kSpaceRight - 3, kSpacing + 2), dst);
 }
 
 /**
@@ -687,7 +686,7 @@
 
 	// Print the min value
 	draw_value((boost::format("-%u") % (highest_scale_)).str(), RGBColor(125, 0, 0),
-	           Vector2f(get_inner_w() - kSpaceRight - 3, get_inner_h() - kSpacing - 23), dst);
+	           Vector2i(get_inner_w() - kSpaceRight - 3, get_inner_h() - kSpacing - 23), dst);
 }
 
 /**

=== modified file 'src/wui/warehousewindow.cc'
--- src/wui/warehousewindow.cc	2017-02-27 18:28:39 +0000
+++ src/wui/warehousewindow.cc	2017-05-11 13:11:27 +0000
@@ -94,7 +94,7 @@
 	}
 	assert(pic != nullptr);
 
-	dst.blit(ware_position(ware).cast<float>(), pic);
+	dst.blit(ware_position(ware), pic);
 }
 
 /**

=== modified file 'src/wui/waresdisplay.cc'
--- src/wui/waresdisplay.cc	2017-04-30 08:40:08 +0000
+++ src/wui/waresdisplay.cc	2017-05-11 13:11:27 +0000
@@ -320,22 +320,22 @@
 	                                                        "images/wui/ware_list_bg.png");
 	uint16_t w = bgpic->width();
 
-	const Vector2f p = ware_position(id).cast<float>();
+	const Vector2i p = ware_position(id);
 	dst.blit(p, bgpic);
 
 	const Image* icon = type_ == Widelands::wwWORKER ? tribe_.get_worker_descr(id)->icon() :
 	                                                   tribe_.get_ware_descr(id)->icon();
 
-	dst.blit(p + Vector2f((w - WARE_MENU_PIC_WIDTH) / 2.f, 1.f), icon);
+	dst.blit(p + Vector2i((w - WARE_MENU_PIC_WIDTH) / 2, 1), icon);
 
-	dst.fill_rect(Rectf(p + Vector2f(0.f, WARE_MENU_PIC_HEIGHT), w, WARE_MENU_INFO_SIZE),
+	dst.fill_rect(Rectf(p + Vector2i(0.f, 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 + Vector2f(w - text->width() - 1,
-		                      WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + 1 - text->height()),
-		         text);
+	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()),
+	   Recti(0, 0, rendered_text->width(), rendered_text->height()));
 }
 
 // Wares highlighting/selecting


Follow ups