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

Proposed by GunChleoc
Status: Merged
Merged at revision: 8369
Proposed branch: lp:~widelands-dev/widelands/fh1-multitexture
Merge into: lp:widelands
Diff against target: 3418 lines (+1178/-714)
64 files modified
src/editor/ui_menus/main_menu_map_options.cc (+1/-3)
src/editor/ui_menus/main_menu_random_map.cc (+1/-3)
src/editor/ui_menus/tool_set_terrain_options_menu.cc (+3/-3)
src/graphic/CMakeLists.txt (+0/-9)
src/graphic/animation.cc (+2/-1)
src/graphic/build_texture_atlas.cc (+0/-4)
src/graphic/font_handler1.cc (+49/-87)
src/graphic/font_handler1.h (+7/-13)
src/graphic/graphic.h (+4/-0)
src/graphic/minimap_renderer.cc (+2/-1)
src/graphic/playercolor.cc (+2/-2)
src/graphic/rendertarget.cc (+18/-6)
src/graphic/rendertarget.h (+8/-2)
src/graphic/text/CMakeLists.txt (+4/-0)
src/graphic/text/rendered_text.cc (+266/-0)
src/graphic/text/rendered_text.h (+174/-0)
src/graphic/text/rt_render.cc (+260/-132)
src/graphic/text/rt_render.h (+6/-7)
src/graphic/text/sdl_ttf_font.cc (+10/-9)
src/graphic/text/sdl_ttf_font.h (+5/-3)
src/graphic/text/test/CMakeLists.txt (+0/-1)
src/graphic/text/test/render.cc (+1/-1)
src/graphic/text/test/render_richtext.cc (+7/-2)
src/graphic/text/texture_cache.h (+39/-0)
src/graphic/text/transient_cache.h (+106/-29)
src/graphic/text_layout.cc (+16/-8)
src/graphic/text_layout.h (+9/-9)
src/graphic/texture.cc (+1/-1)
src/graphic/texture_atlas.cc (+2/-2)
src/graphic/texture_cache.cc (+0/-87)
src/graphic/wordwrap.cc (+10/-14)
src/graphic/wordwrap.h (+1/-1)
src/logic/editor_game_base.h (+2/-2)
src/logic/map_objects/map_object.cc (+11/-14)
src/network/network.h (+1/-1)
src/ui_basic/button.cc (+4/-7)
src/ui_basic/checkbox.cc (+8/-8)
src/ui_basic/checkbox.h (+1/-1)
src/ui_basic/editbox.cc (+10/-23)
src/ui_basic/listselect.cc (+10/-27)
src/ui_basic/messagebox.cc (+2/-2)
src/ui_basic/multilineeditbox.cc (+1/-1)
src/ui_basic/multilinetextarea.cc (+10/-23)
src/ui_basic/panel.cc (+7/-6)
src/ui_basic/panel.h (+3/-0)
src/ui_basic/progressbar.cc (+2/-3)
src/ui_basic/progresswindow.cc (+3/-5)
src/ui_basic/slider.cc (+6/-17)
src/ui_basic/table.cc (+18/-36)
src/ui_basic/tabpanel.cc (+17/-18)
src/ui_basic/tabpanel.h (+2/-4)
src/ui_basic/textarea.cc (+3/-6)
src/ui_basic/textarea.h (+1/-1)
src/ui_basic/window.cc (+2/-3)
src/ui_fsmenu/loadgame.cc (+1/-11)
src/wui/chatoverlay.cc (+5/-7)
src/wui/game_tips.cc (+9/-12)
src/wui/interactive_base.cc (+7/-10)
src/wui/interactive_base.h (+2/-2)
src/wui/interactive_gamebase.cc (+2/-4)
src/wui/mapdetails.cc (+1/-3)
src/wui/maptable.cc (+1/-1)
src/wui/plot_area.cc (+8/-11)
src/wui/waresdisplay.cc (+4/-5)
To merge this branch: bzr merge lp:~widelands-dev/widelands/fh1-multitexture
Reviewer Review Type Date Requested Status
Klaus Halfmann code review, compile, test Approve
kaputtnik (community) testing Approve
GunChleoc Needs Resubmitting
Review via email: mp+323903@code.launchpad.net

Commit message

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

- Fixed positioning and scaling for census & statistics.
- Removed size restriction from FullscreenMenuLoadGame::filename_list_string().
- Got rid of BiDi spaghetti code in Table and Listselect
- As a side effect, this fixes the lag in Multilineeditbox with large texts and reduces the amount of needed cache memory.

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

Description of the change

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.

To post a comment you must log in.
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2172. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/231241186.
Appveyor build 2007. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_fh1_multitexture-2007.

Revision history for this message
kaputtnik (franku) wrote :

Got a compiler warning:

/home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/text/rendered_text.cc:129:14: Warnung: unused variable »maximum_size« [-Wunused-variable]
    const int maximum_size = kMinimumSizeForTextures;
              ^~~~~~~~~~~~

And some crashes, but i am not sure if they are related to this branch, because trunk crashes also sometimes without a reason. One crash in this branch shows in console:

SaveHandler::save_game() took 659ms
Autosave: save took 660 ms
pure virtual method called
terminate called without an active exception
Abgebrochen (Speicherabzug geschrieben

Will try to get a backtrace...

Revision history for this message
kaputtnik (franku) wrote :

In the message box strings in the title column are not cut if they exceed the column width. Played the Warfare tutorial and the message 'Enemy defeated' is only cut at the image from the Type-column, not where the Title-column ends.

No crash so far... every time running with gdb i get no crash :-D

Revision history for this message
GunChleoc (gunchleoc) wrote :

Fixed the cropping, so this is now ready for review again.

review: Needs Resubmitting
Revision history for this message
kaputtnik (franku) wrote :
Download full text (3.6 KiB)

Got a crash:

Thread 1 "widelands" received signal SIGSEGV, Segmentation fault.
0x0000000000cfe5fd in RenderTarget::blit (this=0x1c03930, dst=..., image=0x803d538, blend_mode=BlendMode::UseAlpha,
    align=UI::Align::kLeft) at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/rendertarget.cc:151
151 UI::correct_for_align(align, image->width(), &destination_point);
(gdb) bt
#0 0x0000000000cfe5fd in RenderTarget::blit (this=0x1c03930, dst=..., image=0x803d538, blend_mode=BlendMode::UseAlpha,
    align=UI::Align::kLeft) at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/rendertarget.cc:151
#1 0x0000000000d4a5b1 in UI::RenderedText::draw (this=0x88adfb0, dst=..., position=..., region=..., align=UI::Align::kRight,
    cropmode=UI::RenderedText::CropMode::kRenderTarget)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/text/rendered_text.cc:174
#2 0x0000000000d4a75e in UI::RenderedText::draw (this=0x88adfb0, dst=..., position=..., align=UI::Align::kRight)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/text/rendered_text.cc:191
#3 0x0000000000fac3b9 in InteractiveGameBase::draw_overlay (this=0x9c77ce0, dst=...)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/wui/interactive_gamebase.cc:130
#4 0x0000000000effd10 in UI::Panel::do_draw_inner (this=0x9c77ce0, dst=...)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.cc:733
#5 0x0000000000effe7d in UI::Panel::do_draw (this=0x9c77ce0, dst=...)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.cc:760
#6 0x0000000000efeafe in UI::Panel::do_run (this=0x9c77ce0)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.cc:192
#7 0x0000000000c578a0 in UI::Panel::run<UI::Panel::Returncodes> (this=0x9c77ce0)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.h:99
#8 0x0000000000d6b046 in Widelands::Game::run (this=0x7fffffffbe90, loader_ui=0x7fffffffa280, start_game_type=Widelands::Game::Loaded,
    script_to_run="", replay=false, prefix_for_replays="single_player")
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/logic/game.cc:524
#9 0x0000000000d6a4a0 in Widelands::Game::run_load_game (this=0x7fffffffbe90, filename="save/test.wgf", script_to_run="")
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/logic/game.cc:381
#10 0x0000000000c52396 in WLApplication::load_game (this=0x1991790)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/wlapplication.cc:1278
#11 0x0000000000c513ee in WLApplication::mainmenu_singleplayer (this=0x1991790)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/wlapplication.cc:1107
#12 0x0000000000c50dd8...

Read more...

Revision history for this message
kaputtnik (franku) wrote :

Got another crash in RenderTarget::blit() related to kCensus and kStatistics similar to https://bugs.launchpad.net/widelands/+bug/1690519/comments/6

Those bugs appear after playing a while. Could not say if i played an hour or longer using mostly 3x speed or 5x speed in fullscreen mode (initial resolution 1440x900).

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

I think that render_cache_ need a bit more attention:
 * explian what this actually maps
 * what is the cachek poliy (e.g. when will object be removed?)
 * why do you use an (additional) std::unique_ptr if the make already makes this unique?
 * if its not small it deserverves some presizing.

Some comments inline, but I need more time to read this ....

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for taking on this review :)

 * explian what this actually maps

It keeps rendered text so that we won't have to render it again - rendering is expensive, especially for complex stuff like the help windows. And some text images are reused a lot, e.g. the census and statistics strings.

 * what is the cachek poliy (e.g. when will object be removed?)

Just like with the main image cache and the animation cache, objects are never removed. I guess we could have a flush function if the memory consumption should get too big and that could be called at strategic points, e.g. when switching between fullscreen menu and game.

 * why do you use an (additional) std::unique_ptr if the make already makes this unique?

I don't know what you mean. Are you misreading make_pair as make_unique here?

 * if its not small it deserverves some presizing.

You mean as is already done in texture_cache_(new TextureCache(kTextureCacheSize))?

I have also added comments to your in-line comments.

Revision history for this message
GunChleoc (gunchleoc) wrote :

@kaputtnik: Yes, that looks like the same crash.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I have now looked at the code for the TextureCache (which I didn't write) and it does indeed automatically drop the oldest entries to ensure that its size is not exceeded.

I have also added some asserts (to help track down the crash), and made the nullptr comparisons explicit (maybe this will make it go away).

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Gun: please add the comments in the code,
the next reader will not find it here :-)

What I did not get about the cache is:
For every Text (and width) it stores the Image to render it.

So If I open all dialogs in every language one after the other,
the cache will contain all the Images? Will this include
user generated strings as well? Such szenarios could make
the cache explode.

In general such cahes should me somewhat elastic:
* user LRU to drop old, rarely used entries
* reduze size when memory is getting low
  (e.g. malloc starts to retun null)

Do you have some debugoutput about the size consumptions?
Is there some tescase that switches through all lanaguages
and open all dialogs?

P.S. I now got a newer, faster mac, but I needed today to migrate everything ...

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

On tip about int <-> flot conversion:
As of modern processsors this is quiten expensive,
as this involves the otherwise decupled int and float computing unita.

e.g. Rectf(0.0f, 0.0f, width, height) would be better.

In this particular case the compiler should optimize this away of cause.

lets go on reading the code.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Checked the lower 50% of the chnages now, still not complete.

The code reads much better (some exceptions inline).

please comment what happens with scaling and in the chat window.

will try to play now with different scalings and check lots of dialogs
(english and german only).

Will the font cache be active inside a Gmae only of for the liftime of
the Programm?

Revision history for this message
kaputtnik (franku) wrote :
Download full text (4.5 KiB)

Crashed again (r8200) after playing a while. Just followed an expedition ship exploring the world. Opened windows: Stock and Expedition. Unfortunately no asserts:

Autosave: save took 1591 ms
TrainingSite::drop_stalled_soldiers: Kicking somebody out.
TrainingSite::drop_stalled_soldiers: Kicking somebody out.
TrainingSite::drop_stalled_soldiers: Kicking somebody out.
TrainingSite::drop_stalled_soldiers: Kicking somebody out.

Thread 1 "widelands" received signal SIGSEGV, Segmentation fault.
0x0000000007abea00 in ?? ()
(gdb) bt
#0 0x0000000007abea00 in ?? ()
#1 0x0000000000cff86f in RenderTarget::blit (this=0x78078b0, dst=..., image=0x7abea18, blend_mode=BlendMode::UseAlpha,
    align=UI::Align::kLeft) at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/rendertarget.cc:152
#2 0x0000000000d4b86f in UI::RenderedText::draw (this=0x7a3e950, dst=..., position=..., region=..., align=UI::Align::kCenter,
    cropmode=UI::RenderedText::CropMode::kRenderTarget)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/text/rendered_text.cc:174
#3 0x0000000000d4ba1c in UI::RenderedText::draw (this=0x7a3e950, dst=..., position=..., align=UI::Align::kCenter)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/graphic/text/rendered_text.cc:191
#4 0x0000000000f1f907 in UI::Window::draw_border (this=0x7d0b9e0, dst=...)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/window.cc:281
#5 0x0000000000f010f9 in UI::Panel::do_draw (this=0x7d0b9e0, dst=...)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.cc:754
#6 0x0000000000f01004 in UI::Panel::do_draw_inner (this=0x2331ee0, dst=...)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.cc:731
#7 0x0000000000f0119d in UI::Panel::do_draw (this=0x2331ee0, dst=...)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.cc:760
#8 0x0000000000effe1e in UI::Panel::do_run (this=0x2331ee0)
    at /home/kaputtnik/Quellcode/widelands-repo/fh1-multitexture/src/ui_basic/panel.cc:192
#9 0x0000000000c58af0 in UI::Panel::run<UI::Panel::Returnc...

Read more...

Revision history for this message
GunChleoc (gunchleoc) wrote :

If you click on "Show diff comments" for a post, the diff will switch to the appropriate revision so you can see them.

Answers to your comments inline - I added all important comments to the code as well. I will also replace the 0's with 0.f for the Rectf calls.

I think the caching model is generally broken and also the cause of the cache. The textures are kept in a cache that can overflow, so the RenderedText has references to objects that have been destroyed by the TextureCache. I think that I need to do the following 2 things:

- Turn the TextureCache into a templated object por mimic the code, so we will have an upper size limit for both the small textures within a text and the while RenderedText, and maximum reuse of computed textures at the same time,

- Replace unique_ptr with shared_ptr. This way, when an texture is removed from the cache, it won't be destroyed if a RenderedText is still using it. Furthermore, some UI elements prerender their texts and keep them for their lifetime, so those objects need to share ownership as well. I think the only reason that this particular problem hasn't blown up in our faces yet is that the texture cache never overflows in Build 19.

Revision history for this message
GunChleoc (gunchleoc) wrote :

OK, this went faster than expected. We now have a templated class TransientCache that hands out shared_ptrs to everyone. It's a bit heavy in the header, but there was no obvious split into header/cc file because of the templating.

I have also added some log output when the cache overflows. I haven't seen a log message yet, but that might change once we move more stuff to the new renderer. The cache size can be tweaked then.

Revision history for this message
GunChleoc (gunchleoc) wrote :

Added some more small tweaks, and this is hopefully done now. Let's get a new diff.

review: Needs Resubmitting
Revision history for this message
bunnybot (widelandsofficial) wrote :

Continuous integration builds have changed state:

Travis build 2237. State: passed. Details: https://travis-ci.org/widelands/widelands/builds/234607244.
Appveyor build 2072. State: success. Details: https://ci.appveyor.com/project/widelands-dev/widelands/build/_widelands_dev_widelands_fh1_multitexture-2072.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

OK, compile your code but will now go down to kTextureCacheSize = 5 << 20
or even further untill I find some logs and/or the cache flutters.

Now lets start reading again ...

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Checked the code from the top to about 50%, some comments inline.
Did not find the logging, yet.

wil now play a bit with the reduced cache size.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Ahh, now I see what the cache ist used for:

TransientCache: Dropping 2 bytes, new size 4094. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:08</font></p></rt>
TransientCache: Dropping 2 bytes, new size 4092. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:09</font></p></rt>
TransientCache: Dropping 2 bytes, new size 4090. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:10</font></p></rt>
TransientCache: Dropping 2 bytes, new size 4088. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:11</font></p></rt>
TransientCache: Dropping 2 bytes, new size 4086. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:12</font></p></rt>
TransientCache: Dropping 1 bytes, new size 4085. Hash: 317<rt><p align=left><font face=sans size=14 bold=1 shadow=1 color=ffff00></font></p></rt>
TransientCache: Dropping 2 bytes, new size 4083. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:13</font></p></rt>
TransientCache: Dropping 2 bytes, new size 4081. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:14</font></p></rt>
TransientCache: Dropping 2 bytes, new size 4079. Hash: 0<rt><p align=left><font face=condensed size=14 bold=1 shadow=1 color=ffff00>05:15</font></p></rt>
TransientCache: Dropping 4176 bytes, new size 5230440. Hash: ttf:DejaVu/DejaVuSans-Bold.ttf:14:Teams::ffff00:8
TransientCache: Dropping 4 bytes, new size 4091. Hash: 0<rt><p align=left><font face=sans size=13 bold=1 shadow=1 color=ffff00>Nachrichten: Posteingang</font></p></rt>
TransientCache: Dropping 3744 bytes, new size 5236416. Hash: ttf:DejaVu/DejaVuSans-Bold.ttf:14:Autor::ffff00:8

So this is the gametime, the fps display and such.

Is this wise?
Do you have a chache hit ratio computed somewhere?

Me will ponder this the next days :-)

Revision history for this message
kaputtnik (franku) wrote :

This looks really good now :-)

Played an hour or so but no crash anymore. The output of 'new size' is a bit annoying, i guess it switches between Kilobytes and Bytes (?):

TransientCache: Dropping 11 bytes, new size 4082. [...]
TransientCache: Dropping 4032 bytes, new size 31452972. [...]
[...]
TransientCache: Dropping 4 bytes, new size 4092.

But it never exceeds the size of 4093 (as far as i can see).

There are a lot of such outputs if you just move the mouse around, also moving the mouse on black areas. Don't know if this much processor time consuming. Maybe here is a chance of optimization?

review: Approve (testing)
Revision history for this message
kaputtnik (franku) wrote :

Ah, forgotton:

There is still the compiler warning about the unused variable 'maximum_size' as decribed in my first post here.

Revision history for this message
GunChleoc (gunchleoc) wrote :

I have now removed all the temporary testing stuff, so the compiler warning and the "TransientCache: Dropping" output should be gone now.

I have only commented out the log output in "graphic/text/transient_cache.h", so it can easily be activated again for further tweaking of the cache size.

From my testing, the current cache sizes allow for loading 22 maps in the map selection screen before they start dropping entries. In-game dropping of textures only started after quite a lot of text was generated by mousing over things. Typing 500+ characters in a Multilineeditbox did not trigger texture dropping.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

I got some compile warning we should not neglect:

fh1-multitexture/src/graphic/text/transient_cache.h:48:2: warning: 'TransientCache<Image>' has virtual functions but non-virtual
      destructor [-Wnon-virtual-dtor]
        ~TransientCache();

in general when subclassing and using a parent pointer the correct Destructor will not be called.

fh1-multitexture/src/logic/editor_game_base.h:39:1: warning: struct 'FullscreenMenuLaunchGame' was previously declared as a class
      [-Wmismatched-tags]
struct FullscreenMenuLaunchGame;
^
fh1-multitexture/src/ui_fsmenu/launch_game.h:44:7: note: previous use is here
class FullscreenMenuLaunchGame : public FullscreenMenuBase {

fh1-multitexture/src/network/network.h:122:9: warning: struct 'Deserializer' was previously declared as a class [-Wmismatched-tags]
        friend struct Deserializer;
               ^
fh1-multitexture/src/network/network.h:36:7: note: previous use is here
class Deserializer;

Are there functions limited to float only?

fh1-multitexture/src/logic/map_objects/map_object.cc:468:40: warning: implicit conversion increases floating-point precision: 'float' to 'double' [-Wdouble-promotion]
        scale = std::round(2 * (scale > 1.f ? std::sqrt(scale) : std::pow(scale, 2))) / 2;

No idea about these, SirVer may tell us?

fh1-multitexture/src/graphic/font_handler1.cc:90:48: warning: moving a temporary object prevents copy elision [-Wpessimizing-move]
                        rendered_text = render_cache_->insert(hash, std::move(rt_renderer_->render(text, w)));
                                                                    ^
fh1-multitexture/src/graphic/font_handler1.cc:90:48: note: remove std::move call here
                        rendered_text = render_cache_->insert(hash, std::move(rt_renderer_->render(text, w)));

Sorry no time to check more code tonight, will just play that branch a bit ....

Revision history for this message
GunChleoc (gunchleoc) wrote :

Thanks for the review! I have hopefully fixed the compiler warnings now - we'll just have to wait what clang says.

I also addressed your points from a few posts up and added some more documentation to the RendererText header file - I hope it will make the code a bit more clear. Yes, it's a bit complicated due to the cropping involved.

2 notes:

1. CropMode::kVertical doesn't exist because CropMode::kHorizontal was a stupid name. I went with kSelf now. Btter ideas are welcome :)

2. Re. better replace true / false by kBackGroundColorSet and !kBackGroundColorSet or such: While I generally prefer enum classes over bools myself, I think it would actually make the code harder to read in this instance, because there is no "else" branch when it is checked. This bool is only ever assigned in the private constructors, so it shouldn't cause any interface readability issues.

Revision history for this message
GunChleoc (gunchleoc) wrote :

> Do you have a chache hit ratio computed somewhere?

No, I just used the log output to experiment a bit to find an acceptable value. Since we have 2 caches now, I made one really big while testing the other and vice versa.

BTW the gametime being kicked out is perfectly OK, since it counts up, so the old gametime texture definitely isn't needed any more. The cache also bumps entries when they are accessed, so those textures that have gone the longest without being used are the first to be removed when it overflows.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Thx, the warning are gone, we still have some, but I will addres them in the next branch,
so we get get this in.

Will it be possible to exclude some Texts/Textures from Caching?
e.g. the current game time or any other text we know will change the next time we draw it.

will not have time for more code rewiew till perhpas the weekend.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Now I got a crash after playing for perhaps 10 Minutes?

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 widelands 0x00000001042abba5 Widelands::Request::transfer_finish(Widelands::Game&, Widelands::Transfer&) + 69 (request.cc:418)
1 widelands 0x00000001042bc8c2 Widelands::Transfer::has_finished() + 50 (transfer.cc:222)
2 widelands 0x00000001042becad Widelands::WareInstance::enter_building(Widelands::Game&, Widelands::Building&) + 157 (ware_instance.cc:376)
3 widelands 0x0000000103bb77d9 Widelands::Carrier::deliver_to_building(Widelands::Game&, Widelands::Bob::State&) + 409 (carrier.cc:213)
4 widelands 0x0000000103bb72cb Widelands::Carrier::transport_update(Widelands::Game&, Widelands::Bob::State&) + 971 (carrier.cc:160)
5 widelands 0x0000000103b5cb51 Widelands::Bob::do_act(Widelands::Game&) + 337 (bob.cc:196)

Uhm.. perhpas I can reproduce that one?

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

playes another on > 1h, no Problems. so far for determistic machines.

Revision history for this message
GunChleoc (gunchleoc) wrote :

The newest crash looks like it's coming from WareInstance::cleanup - there's a naked delete statement in there. Doesn't seem related to the font renderer.

Revision history for this message
GunChleoc (gunchleoc) wrote :

> Will it be possible to exclude some Texts/Textures from Caching?

I think that would be overkill. This would mean carrying a parameter through quite a lot of functions until it reaches the point where it's checked for, for very little gain.

Let's say our cache looks like this:

A - B - C - D

Then we need B again, this will make the cache like like this:

A - C - D - B

Then let's say it's full and we add something new, the cache will now look like this:

C - D - B - E
D - B - E - F

So, the frequently-used B has not been dropped yet, although C was originally generated after B was generated.

I think this is efficient enough for reuse.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

Will we need a resubmit fro travis to kick in?
Looks I now have time to do a full review and some Debugging.

Revision history for this message
Klaus Halfmann (klaus-halfmann) wrote :

OK for me now, we should get this in and try to find those nasty crashes in trunk.
I still do not get the whole story, though. Would like to see bunnybot again.

Will continue with that net-boost-asio next.

review: Approve (code review, compile, test)
Revision history for this message
GunChleoc (gunchleoc) wrote :

As long as both Travis and Appveyor continue to be green, bunnybot will not post here again. SirVer stopped that, because it was too spammy.

Bunnybot will refuse to merge though if Travis and Appyeyor aren't both green.

Thanks for the testing and review! I know this was a complicated one.

@bunnybot merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/editor/ui_menus/main_menu_map_options.cc'
2--- src/editor/ui_menus/main_menu_map_options.cc 2017-05-13 11:25:24 +0000
3+++ src/editor/ui_menus/main_menu_map_options.cc 2017-05-23 21:33:09 +0000
4@@ -46,9 +46,7 @@
5 : UI::Window(&parent, "map_options", 0, 0, 350, parent.get_inner_h() - 80, _("Map Options")),
6 padding_(4),
7 indent_(10),
8- labelh_(
9- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
10- 4),
11+ labelh_(text_height() + 4),
12 checkbox_space_(25),
13 butw_((get_inner_w() - 3 * padding_) / 2),
14 max_w_(get_inner_w() - 2 * padding_),
15
16=== modified file 'src/editor/ui_menus/main_menu_random_map.cc'
17--- src/editor/ui_menus/main_menu_random_map.cc 2017-05-13 11:25:24 +0000
18+++ src/editor/ui_menus/main_menu_random_map.cc 2017-05-23 21:33:09 +0000
19@@ -52,9 +52,7 @@
20 // UI elements
21 margin_(4),
22 box_width_(get_inner_w() - 2 * margin_),
23- label_height_(
24- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
25- 2),
26+ label_height_(text_height() + 2),
27 box_(this, margin_, margin_, UI::Box::Vertical, 0, 0, margin_),
28 // Size
29 width_(&box_,
30
31=== modified file 'src/editor/ui_menus/tool_set_terrain_options_menu.cc'
32--- src/editor/ui_menus/tool_set_terrain_options_menu.cc 2017-05-13 11:25:24 +0000
33+++ src/editor/ui_menus/tool_set_terrain_options_menu.cc 2017-05-23 21:33:09 +0000
34@@ -53,8 +53,8 @@
35 // Blit the main terrain image
36 const Image& terrain_texture = terrain_descr.get_texture(0);
37 Texture* texture = new Texture(terrain_texture.width(), terrain_texture.height());
38- texture->blit(Rectf(0, 0, terrain_texture.width(), terrain_texture.height()), terrain_texture,
39- Rectf(0, 0, terrain_texture.width(), terrain_texture.height()), 1.,
40+ texture->blit(Rectf(0.f, 0.f, terrain_texture.width(), terrain_texture.height()), terrain_texture,
41+ Rectf(0.f, 0.f, terrain_texture.width(), terrain_texture.height()), 1.,
42 BlendMode::UseAlpha);
43 Vector2i pt(1, terrain_texture.height() - kSmallPicSize - 1);
44
45@@ -66,7 +66,7 @@
46
47 texture->blit(Rectf(pt.x, pt.y, terrain_type.icon->width(), terrain_type.icon->height()),
48 *terrain_type.icon,
49- Rectf(0, 0, terrain_type.icon->width(), terrain_type.icon->height()), 1.,
50+ Rectf(0.f, 0.f, terrain_type.icon->width(), terrain_type.icon->height()), 1.,
51 BlendMode::UseAlpha);
52 pt.x += kSmallPicSize + 1;
53 }
54
55=== modified file 'src/graphic/CMakeLists.txt'
56--- src/graphic/CMakeLists.txt 2017-04-26 17:11:43 +0000
57+++ src/graphic/CMakeLists.txt 2017-05-23 21:33:09 +0000
58@@ -109,8 +109,6 @@
59 surface.h
60 texture.cc
61 texture.h
62- texture_cache.cc
63- texture_cache.h
64 USES_SDL2
65 DEPENDS
66 base_exceptions
67@@ -268,16 +266,9 @@
68 font_handler1.cc
69 font_handler1.h
70 DEPENDS
71- base_exceptions
72- base_geometry
73- base_log
74 base_macros
75- graphic
76- graphic_align
77 graphic_image_cache
78- graphic_surface
79 graphic_text
80- io_filesystem
81 )
82
83 wl_library(graphic_text_constants
84
85=== modified file 'src/graphic/animation.cc'
86--- src/graphic/animation.cc 2017-05-19 06:05:40 +0000
87+++ src/graphic/animation.cc 2017-05-23 21:33:09 +0000
88@@ -227,7 +227,8 @@
89 const int w = image->width();
90 const int h = image->height();
91 Texture* rv = new Texture(w / scale_, h / scale_);
92- rv->blit(Rectf(0, 0, w / scale_, h / scale_), *image, Rectf(0, 0, w, h), 1., BlendMode::Copy);
93+ rv->blit(
94+ Rectf(0.f, 0.f, w / scale_, h / scale_), *image, Rectf(0.f, 0.f, w, h), 1., BlendMode::Copy);
95 return rv;
96 }
97
98
99=== modified file 'src/graphic/build_texture_atlas.cc'
100--- src/graphic/build_texture_atlas.cc 2017-01-25 18:55:59 +0000
101+++ src/graphic/build_texture_atlas.cc 2017-05-23 21:33:09 +0000
102@@ -39,10 +39,6 @@
103 // threshold, but not background pictures.
104 constexpr int kMaxAreaForTextureAtlas = 240 * 240;
105
106-// A graphics card must at least support this size for texture for Widelands to
107-// run.
108-constexpr int kMinimumSizeForTextures = 2048;
109-
110 // Returns true if 'filename' ends with an image extension.
111 bool is_image(const std::string& filename) {
112 return boost::ends_with(filename, ".png") || boost::ends_with(filename, ".jpg");
113
114=== modified file 'src/graphic/font_handler1.cc'
115--- src/graphic/font_handler1.cc 2017-03-25 15:32:49 +0000
116+++ src/graphic/font_handler1.cc 2017-05-23 21:33:09 +0000
117@@ -19,88 +19,34 @@
118
119 #include "graphic/font_handler1.h"
120
121-#include <functional>
122 #include <memory>
123
124 #include <boost/lexical_cast.hpp>
125-#include <boost/utility.hpp>
126
127-#include "base/log.h"
128-#include "base/wexception.h"
129-#include "graphic/graphic.h"
130-#include "graphic/image.h"
131-#include "graphic/image_cache.h"
132-#include "graphic/rendertarget.h"
133-#include "graphic/text/rt_errors.h"
134 #include "graphic/text/rt_render.h"
135-#include "graphic/text/sdl_ttf_font.h"
136-#include "graphic/texture.h"
137-#include "graphic/texture_cache.h"
138-#include "io/filesystem/filesystem.h"
139-
140-using namespace std;
141-using namespace boost;
142+#include "graphic/text/texture_cache.h"
143
144 namespace {
145
146-// The size of the richtext surface cache in bytes. All work that the richtext
147-// renderer does is / cached in this cache until it overflows. The idea is that
148-// this is big enough to cache the text that is used on a typical screen - so
149-// that we do not need to lay out text every frame. Last benchmarked at r7712,
150-// 30 MB was enough to cache texts for many frames (> 1000), while it is
151-// quickly overflowing in the map selection menu.
152-// This might need reevaluation is the new font handler is used for more stuff.
153-const uint32_t RICHTEXT_TEXTURE_CACHE = 30 << 20; // shifting converts to MB
154-
155-// An Image implementation that recreates a rich text texture when needed on
156-// the fly. It is meant to be saved into the ImageCache.
157-class RTImage : public Image {
158-public:
159- RTImage(const string& ghash,
160- TextureCache* texture_cache,
161- std::function<RT::Renderer*()> get_renderer,
162- const string& text,
163- int gwidth)
164- : hash_(ghash),
165- text_(text),
166- width_(gwidth),
167- get_renderer_(get_renderer),
168- texture_cache_(texture_cache) {
169- }
170- virtual ~RTImage() {
171- }
172-
173- // Implements Image.
174- int width() const override {
175- return texture()->width();
176- }
177- int height() const override {
178- return texture()->height();
179- }
180-
181- const BlitData& blit_data() const override {
182- return texture()->blit_data();
183- }
184-
185-private:
186- Texture* texture() const {
187- Texture* surf = texture_cache_->get(hash_);
188- if (surf != nullptr) {
189- return surf;
190- }
191- return texture_cache_->insert(
192- hash_, std::unique_ptr<Texture>(get_renderer_()->render(text_, width_)));
193- }
194-
195- const string hash_;
196- const string text_;
197- int width_;
198- std::function<RT::Renderer*()> get_renderer_;
199-
200- // Nothing owned.
201- TextureCache* const texture_cache_;
202-};
203-}
204+// The CacheSize constexpr values control the cache sizes for our transient caches.
205+// The idea is that they are big enough to cache the text that is used on a typical screen - so
206+// that we do not need to lay out text every frame.
207+
208+// TODO(GunChleoc): Re-evaulate the cache sizes once the in-game help and the campaigns have been
209+// converted to this font renderer.
210+// The current cache sizes allow for loading 22 maps in the map selection screen before they start
211+// dropping entries. In-game dropping of textures only started after quite a lot of text was
212+// generated by mousing over things. Typing 500+ characters in a Multilineeditbox did not trigger
213+// texture dropping.
214+
215+// The size of the richtext surface cache in bytes.
216+constexpr uint32_t kTextureCacheSize = 3 << 20; // shifting by 20 converts to MB
217+
218+// The maximum number of RenderedRects. It's all pointers or combinations of basic data types, so
219+// the size requirement is pretty constant. Therefore, simply counting them is sufficient.
220+// We estimate that the member variables of each RenderedRect take up ca. 13 * 32 bytes.
221+constexpr uint32_t kRenderCacheSize = 8 * 1024;
222+} // namespace
223
224 namespace UI {
225
226@@ -108,9 +54,24 @@
227 // the ImageCache, so repeated calls to render with the same arguments should not
228 // be a problem.
229 class FontHandler1 : public IFontHandler1 {
230+private:
231+ // A transient cache for the generated rendered texts
232+ class RenderCache : public TransientCache<RenderedText> {
233+ public:
234+ RenderCache(uint32_t max_number_of_rects)
235+ : TransientCache<RenderedText>(max_number_of_rects) {
236+ }
237+
238+ std::shared_ptr<const RenderedText> insert(const std::string& hash,
239+ std::shared_ptr<const RenderedText> entry) override {
240+ return TransientCache<RenderedText>::insert(hash, entry, entry->rects.size());
241+ }
242+ };
243+
244 public:
245 FontHandler1(ImageCache* image_cache, const std::string& locale)
246- : texture_cache_(new TextureCache(RICHTEXT_TEXTURE_CACHE)),
247+ : texture_cache_(new TextureCache(kTextureCacheSize)),
248+ render_cache_(new RenderCache(kRenderCacheSize)),
249 fontsets_(),
250 fontset_(fontsets_.get_fontset(locale)),
251 rt_renderer_(new RT::Renderer(image_cache, texture_cache_.get(), fontsets_)),
252@@ -119,17 +80,16 @@
253 virtual ~FontHandler1() {
254 }
255
256- const Image* render(const string& text, uint16_t w = 0) override {
257- const string hash = boost::lexical_cast<string>(w) + text;
258-
259- if (image_cache_->has(hash))
260- return image_cache_->get(hash);
261-
262- std::unique_ptr<RTImage> image(
263- new RTImage(hash, texture_cache_.get(), [this] { return rt_renderer_.get(); }, text, w));
264- image->width(); // force the rich text to get rendered in case there is an exception thrown.
265-
266- return image_cache_->insert(hash, std::move(image));
267+ // This will render the 'text' with a width restriction of 'w'. If 'w' == 0, no restriction is
268+ // applied.
269+ std::shared_ptr<const UI::RenderedText> render(const std::string& text,
270+ uint16_t w = 0) override {
271+ const std::string hash = boost::lexical_cast<std::string>(w) + text;
272+ std::shared_ptr<const RenderedText> rendered_text = render_cache_->get(hash);
273+ if (rendered_text.get() == nullptr) {
274+ rendered_text = render_cache_->insert(hash, rt_renderer_->render(text, w));
275+ }
276+ return rendered_text;
277 }
278
279 UI::FontSet const* fontset() const override {
280@@ -138,12 +98,14 @@
281
282 void reinitialize_fontset(const std::string& locale) override {
283 fontset_ = fontsets_.get_fontset(locale);
284- texture_cache_.get()->flush();
285+ texture_cache_->flush();
286+ render_cache_->flush();
287 rt_renderer_.reset(new RT::Renderer(image_cache_, texture_cache_.get(), fontsets_));
288 }
289
290 private:
291 std::unique_ptr<TextureCache> texture_cache_;
292+ std::unique_ptr<RenderCache> render_cache_;
293 UI::FontSets fontsets_; // All fontsets
294 UI::FontSet const* fontset_; // The currently active FontSet
295 std::unique_ptr<RT::Renderer> rt_renderer_;
296
297=== modified file 'src/graphic/font_handler1.h'
298--- src/graphic/font_handler1.h 2017-02-28 20:07:07 +0000
299+++ src/graphic/font_handler1.h 2017-05-23 21:33:09 +0000
300@@ -22,16 +22,11 @@
301 #define WL_GRAPHIC_FONT_HANDLER1_H
302
303 #include <memory>
304-#include <string>
305
306 #include "base/macros.h"
307-#include "base/vector.h"
308-#include "graphic/align.h"
309+#include "graphic/image_cache.h"
310 #include "graphic/text/font_set.h"
311-
312-class FileSystem;
313-class Image;
314-class ImageCache;
315+#include "graphic/text/rendered_text.h"
316
317 namespace UI {
318
319@@ -44,11 +39,10 @@
320 virtual ~IFontHandler1() {
321 }
322
323- /*
324- * Renders the given text into an image. The image is cached and therefore
325- * ownership remains with this class. Will throw on error.
326- */
327- virtual const Image* render(const std::string& text, uint16_t w = 0) = 0;
328+ /// Renders the given text into a set of images. The images are cached in a transient cache,
329+ /// so we share the ownership. Will throw on error.
330+ virtual std::shared_ptr<const UI::RenderedText> render(const std::string& text,
331+ uint16_t w = 0) = 0;
332
333 /// Returns the font handler's current FontSet
334 virtual UI::FontSet const* fontset() const = 0;
335@@ -61,7 +55,7 @@
336 DISALLOW_COPY_AND_ASSIGN(IFontHandler1);
337 };
338
339-// Create a new FontHandler1.
340+/// Create a new FontHandler1.
341 IFontHandler1* create_fonthandler(ImageCache* image_cache, const std::string& locale);
342
343 extern IFontHandler1* g_fh1;
344
345=== modified file 'src/graphic/graphic.h'
346--- src/graphic/graphic.h 2017-01-25 18:55:59 +0000
347+++ src/graphic/graphic.h 2017-05-23 21:33:09 +0000
348@@ -33,6 +33,10 @@
349 class Screen;
350 class StreamWrite;
351
352+// A graphics card must at least support this size for texture for Widelands to
353+// run.
354+constexpr int kMinimumSizeForTextures = 2048;
355+
356 // Will be send whenever the resolution changes.
357 struct GraphicResolutionChanged {
358 CAN_BE_SENT_AS_NOTE(NoteId::GraphicResolutionChanged)
359
360=== modified file 'src/graphic/minimap_renderer.cc'
361--- src/graphic/minimap_renderer.cc 2017-05-13 11:25:24 +0000
362+++ src/graphic/minimap_renderer.cc 2017-05-23 21:33:09 +0000
363@@ -237,7 +237,8 @@
364
365 std::unique_ptr<Texture> texture(new Texture(map_w, map_h));
366
367- texture->fill_rect(Rectf(0, 0, texture->width(), texture->height()), RGBAColor(0, 0, 0, 255));
368+ texture->fill_rect(
369+ Rectf(0.f, 0.f, texture->width(), texture->height()), RGBAColor(0, 0, 0, 255));
370
371 // Center the view on the middle of the 'view_area'.
372 const bool zoom = layers & MiniMapLayer::Zoom2;
373
374=== modified file 'src/graphic/playercolor.cc'
375--- src/graphic/playercolor.cc 2017-05-15 11:36:22 +0000
376+++ src/graphic/playercolor.cc 2017-05-23 21:33:09 +0000
377@@ -49,8 +49,8 @@
378 const int w = image->width();
379 const int h = image->height();
380 Texture* pc_image = new Texture(w, h);
381- pc_image->fill_rect(Rectf(0, 0, w, h), RGBAColor(0, 0, 0, 0));
382- pc_image->blit_blended(Rectf(0, 0, w, h), *image, *color_mask, Rectf(0, 0, w, h), clr);
383+ pc_image->fill_rect(Rectf(0.f, 0.f, w, h), RGBAColor(0, 0, 0, 0));
384+ pc_image->blit_blended(Rectf(0.f, 0.f, w, h), *image, *color_mask, Rectf(0.f, 0.f, w, h), clr);
385 g_gr->images().insert(hash, std::unique_ptr<const Texture>(std::move(pc_image)));
386 assert(g_gr->images().has(hash));
387 return g_gr->images().get(hash);
388
389=== modified file 'src/graphic/rendertarget.cc'
390--- src/graphic/rendertarget.cc 2017-05-13 18:48:26 +0000
391+++ src/graphic/rendertarget.cc 2017-05-23 21:33:09 +0000
392@@ -23,6 +23,7 @@
393 #include "graphic/animation.h"
394 #include "graphic/graphic.h"
395 #include "graphic/surface.h"
396+#include "graphic/text_layout.h"
397
398 /**
399 * Build a render target for the given surface.
400@@ -142,9 +143,16 @@
401 *
402 * This blit function copies the pixels to the destination surface.
403 */
404-void RenderTarget::blit(const Vector2i& dst, const Image* image, BlendMode blend_mode) {
405- Rectf source_rect(Vector2i::zero(), image->width(), image->height());
406- Rectf destination_rect(dst.x, dst.y, source_rect.w, source_rect.h);
407+void RenderTarget::blit(const Vector2i& dst,
408+ const Image* image,
409+ BlendMode blend_mode,
410+ UI::Align align) {
411+ assert(image != nullptr);
412+ Vector2i destination_point(dst);
413+ UI::correct_for_align(align, image->width(), &destination_point);
414+
415+ Rectf source_rect(0.f, 0.f, image->width(), image->height());
416+ Rectf destination_rect(destination_point.x, destination_point.y, source_rect.w, source_rect.h);
417
418 if (to_surface_geometry(&destination_rect, &source_rect)) {
419 // I seem to remember seeing 1. a lot in blitting calls.
420@@ -155,9 +163,13 @@
421
422 void RenderTarget::blit_monochrome(const Vector2i& dst,
423 const Image* image,
424- const RGBAColor& blend_mode) {
425- Rectf source_rect(Vector2i::zero(), image->width(), image->height());
426- Rectf destination_rect(dst.x, dst.y, source_rect.w, source_rect.h);
427+ const RGBAColor& blend_mode,
428+ UI::Align align) {
429+ Vector2i destination_point(dst);
430+ UI::correct_for_align(align, image->width(), &destination_point);
431+
432+ Rectf source_rect(0.f, 0.f, image->width(), image->height());
433+ Rectf destination_rect(destination_point.x, destination_point.y, source_rect.w, source_rect.h);
434
435 if (to_surface_geometry(&destination_rect, &source_rect)) {
436 surface_->blit_monochrome(destination_rect, *image, source_rect, blend_mode);
437
438=== modified file 'src/graphic/rendertarget.h'
439--- src/graphic/rendertarget.h 2017-05-13 18:48:26 +0000
440+++ src/graphic/rendertarget.h 2017-05-23 21:33:09 +0000
441@@ -62,10 +62,16 @@
442 void fill_rect(const Recti&, const RGBAColor&, BlendMode blend_mode = BlendMode::Copy);
443 void brighten_rect(const Recti&, int32_t factor);
444
445- void blit(const Vector2i& dst, const Image* image, BlendMode blend_mode = BlendMode::UseAlpha);
446+ void blit(const Vector2i& dst,
447+ const Image* image,
448+ BlendMode blend_mode = BlendMode::UseAlpha,
449+ UI::Align = UI::Align::kLeft);
450
451 // Like blit. See MonochromeBlitProgram for details.
452- void blit_monochrome(const Vector2i& dst, const Image* image, const RGBAColor& blend_mode);
453+ void blit_monochrome(const Vector2i& dst,
454+ const Image* image,
455+ const RGBAColor& blend_mode,
456+ UI::Align = UI::Align::kLeft);
457
458 void blitrect(const Vector2i& dst,
459 const Image* image,
460
461=== modified file 'src/graphic/text/CMakeLists.txt'
462--- src/graphic/text/CMakeLists.txt 2017-03-29 12:36:20 +0000
463+++ src/graphic/text/CMakeLists.txt 2017-05-23 21:33:09 +0000
464@@ -14,10 +14,14 @@
465 rt_parse.h
466 rt_render.cc
467 rt_render.h
468+ rendered_text.cc
469+ rendered_text.h
470 sdl_ttf_font.cc
471 sdl_ttf_font.h
472 textstream.cc
473 textstream.h
474+ texture_cache.h
475+ transient_cache.h
476 USES_SDL2
477 USES_SDL2_TTF
478 USES_ICU
479
480=== added file 'src/graphic/text/rendered_text.cc'
481--- src/graphic/text/rendered_text.cc 1970-01-01 00:00:00 +0000
482+++ src/graphic/text/rendered_text.cc 2017-05-23 21:33:09 +0000
483@@ -0,0 +1,266 @@
484+/*
485+ * Copyright (C) 2017 by the Widelands Development Team
486+ *
487+ * This program is free software; you can redistribute it and/or
488+ * modify it under the terms of the GNU General Public License
489+ * as published by the Free Software Foundation; either version 2
490+ * of the License, or (at your option) any later version.
491+ *
492+ * This program is distributed in the hope that it will be useful,
493+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
494+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
495+ * GNU General Public License for more details.
496+ *
497+ * You should have received a copy of the GNU General Public License
498+ * along with this program; if not, write to the Free Software
499+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
500+ *
501+ */
502+
503+#include "graphic/text/rendered_text.h"
504+
505+#include <memory>
506+
507+#include "graphic/graphic.h"
508+#include "graphic/text_layout.h"
509+
510+namespace UI {
511+// RenderedRect
512+RenderedRect::RenderedRect(const Recti& init_rect,
513+ std::shared_ptr<const Image> init_image,
514+ bool visited,
515+ const RGBColor& color,
516+ bool is_background_color_set,
517+ DrawMode init_mode)
518+ : rect_(init_rect),
519+ transient_image_(init_image),
520+ permanent_image_(nullptr),
521+ visited_(visited),
522+ background_color_(color),
523+ is_background_color_set_(is_background_color_set),
524+ mode_(init_mode) {
525+}
526+RenderedRect::RenderedRect(const Recti& init_rect,
527+ const Image* init_image,
528+ bool visited,
529+ const RGBColor& color,
530+ bool is_background_color_set,
531+ DrawMode init_mode)
532+ : rect_(init_rect),
533+ transient_image_(nullptr),
534+ permanent_image_(init_image),
535+ visited_(visited),
536+ background_color_(color),
537+ is_background_color_set_(is_background_color_set),
538+ mode_(init_mode) {
539+}
540+
541+RenderedRect::RenderedRect(const Recti& init_rect, const Image* init_image)
542+ : RenderedRect(init_rect, init_image, false, RGBColor(0, 0, 0), false, DrawMode::kTile) {
543+}
544+RenderedRect::RenderedRect(const Recti& init_rect, const RGBColor& color)
545+ : RenderedRect(init_rect, nullptr, false, color, true, DrawMode::kTile) {
546+}
547+RenderedRect::RenderedRect(std::shared_ptr<const Image> init_image)
548+ : RenderedRect(Recti(0, 0, init_image->width(), init_image->height()),
549+ init_image,
550+ false,
551+ RGBColor(0, 0, 0),
552+ false,
553+ DrawMode::kBlit) {
554+}
555+RenderedRect::RenderedRect(const Image* init_image)
556+ : RenderedRect(Recti(0, 0, init_image->width(), init_image->height()),
557+ init_image,
558+ false,
559+ RGBColor(0, 0, 0),
560+ false,
561+ DrawMode::kBlit) {
562+}
563+
564+const Image* RenderedRect::image() const {
565+ assert(permanent_image_ == nullptr || transient_image_.get() == nullptr);
566+ return permanent_image_ == nullptr ? transient_image_.get() : permanent_image_;
567+}
568+
569+int RenderedRect::x() const {
570+ return rect_.x;
571+}
572+
573+int RenderedRect::y() const {
574+ return rect_.y;
575+}
576+
577+int RenderedRect::width() const {
578+ return rect_.w;
579+}
580+int RenderedRect::height() const {
581+ return rect_.h;
582+}
583+
584+void RenderedRect::set_origin(const Vector2i& new_origin) {
585+ rect_.x = new_origin.x;
586+ rect_.y = new_origin.y;
587+}
588+void RenderedRect::set_visited() {
589+ visited_ = true;
590+}
591+bool RenderedRect::was_visited() const {
592+ return visited_;
593+}
594+
595+bool RenderedRect::has_background_color() const {
596+ return is_background_color_set_;
597+}
598+const RGBColor& RenderedRect::background_color() const {
599+ return background_color_;
600+}
601+
602+RenderedRect::DrawMode RenderedRect::mode() const {
603+ return mode_;
604+}
605+
606+// RenderedText
607+int RenderedText::width() const {
608+ int result = 0;
609+ for (const auto& rect : rects) {
610+ result = std::max(result, rect->x() + rect->width());
611+ }
612+ return result;
613+}
614+int RenderedText::height() const {
615+ int result = 0;
616+ for (const auto& rect : rects) {
617+ result = std::max(result, rect->y() + rect->height());
618+ }
619+ return result;
620+}
621+
622+void RenderedText::draw(RenderTarget& dst,
623+ const Vector2i& position,
624+ const Recti& region,
625+ Align align,
626+ CropMode cropmode) const {
627+
628+ // Un-const the position and adjust for alignment according to region width
629+ Vector2i aligned_pos(position.x, position.y);
630+
631+ // For cropping images that don't fit
632+ int offset_x = 0;
633+ if (cropmode == CropMode::kSelf) {
634+ UI::correct_for_align(align, width(), &aligned_pos);
635+ if (align != UI::Align::kLeft) {
636+ for (const auto& rect : rects) {
637+ offset_x = std::min(region.w - rect->width(), offset_x);
638+ }
639+ if (align == UI::Align::kCenter) {
640+ offset_x /= 2;
641+ }
642+ }
643+ } else {
644+ aligned_pos.x -= region.x;
645+ aligned_pos.y -= region.y;
646+ UI::correct_for_align(align, region.w, &aligned_pos);
647+ }
648+
649+ // Blit the rects
650+ for (const auto& rect : rects) {
651+ blit_rect(dst, offset_x, aligned_pos, *rect.get(), region, align, cropmode);
652+ }
653+}
654+
655+void RenderedText::blit_rect(RenderTarget& dst,
656+ int offset_x,
657+ const Vector2i& aligned_position,
658+ const RenderedRect& rect,
659+ const Recti& region,
660+ Align align,
661+ CropMode cropmode) const {
662+ const Vector2i blit_point(aligned_position.x + rect.x(), aligned_position.y + rect.y());
663+
664+ // Draw Solid background Color
665+ if (rect.has_background_color()) {
666+#ifndef NDEBUG
667+ const int maximum_size = kMinimumSizeForTextures;
668+#else
669+ const int maximum_size = g_gr->max_texture_size();
670+#endif
671+ const int tile_width = std::min(maximum_size, rect.width());
672+ const int tile_height = std::min(maximum_size, rect.height());
673+ for (int tile_x = blit_point.x; tile_x + tile_width <= blit_point.x + rect.width();
674+ tile_x += tile_width) {
675+ for (int tile_y = blit_point.y; tile_y + tile_height <= blit_point.y + rect.height();
676+ tile_y += tile_height) {
677+ dst.fill_rect(Recti(tile_x, tile_y, tile_width, tile_height), rect.background_color());
678+ }
679+ }
680+ }
681+
682+ if (rect.image() != nullptr) {
683+ switch (rect.mode()) {
684+ // Draw a foreground texture
685+ case RenderedRect::DrawMode::kBlit: {
686+ switch (cropmode) {
687+ case CropMode::kRenderTarget:
688+ // dst will handle any cropping
689+ dst.blit(blit_point, rect.image());
690+ break;
691+ case CropMode::kSelf:
692+ blit_cropped(dst, offset_x, aligned_position, blit_point, rect, region, align);
693+ }
694+ } break;
695+ // Draw a background image (tiling)
696+ case RenderedRect::DrawMode::kTile:
697+ dst.tile(Recti(blit_point, rect.width(), rect.height()), rect.image(), Vector2i::zero());
698+ break;
699+ }
700+ }
701+}
702+
703+void RenderedText::draw(RenderTarget& dst, const Vector2i& position, UI::Align align) const {
704+ draw(dst, position, Recti(0, 0, width(), height()), align);
705+}
706+
707+// Crop horizontally if it doesn't fit
708+void RenderedText::blit_cropped(RenderTarget& dst,
709+ int offset_x,
710+ const Vector2i& position,
711+ const Vector2i& blit_point,
712+ const RenderedRect& rect,
713+ const Recti& region,
714+ Align align) const {
715+
716+ int blit_width = rect.width();
717+ int cropped_left = 0;
718+ if (align != UI::Align::kLeft) {
719+ if (rect.x() + rect.width() + offset_x <= region.x) {
720+ // Falls off the left-hand side
721+ return;
722+ }
723+ if (rect.x() + offset_x < 0) {
724+ // Needs cropping
725+ blit_width = rect.width() + offset_x + rect.x() - region.x;
726+ cropped_left = rect.width() - blit_width;
727+ }
728+ }
729+
730+ if (align != UI::Align::kRight) {
731+ if (rect.x() + rect.width() - offset_x > region.w - region.x) {
732+ blit_width = region.w - rect.x() - offset_x;
733+ }
734+ }
735+
736+ // Don't blit tiny or negative width
737+ if (blit_width < 3) {
738+ return;
739+ }
740+
741+ dst.blitrect(
742+ Vector2i(cropped_left > 0 ?
743+ position.x + region.x - (align == UI::Align::kRight ? region.w : region.w / 2) :
744+ blit_point.x,
745+ blit_point.y),
746+ rect.image(), Recti(cropped_left > 0 ? cropped_left : 0, region.y, blit_width, region.h));
747+}
748+
749+} // namespace UI
750
751=== added file 'src/graphic/text/rendered_text.h'
752--- src/graphic/text/rendered_text.h 1970-01-01 00:00:00 +0000
753+++ src/graphic/text/rendered_text.h 2017-05-23 21:33:09 +0000
754@@ -0,0 +1,174 @@
755+/*
756+ * Copyright (C) 2017 by the Widelands Development Team
757+ *
758+ * This program is free software; you can redistribute it and/or
759+ * modify it under the terms of the GNU General Public License
760+ * as published by the Free Software Foundation; either version 2
761+ * of the License, or (at your option) any later version.
762+ *
763+ * This program is distributed in the hope that it will be useful,
764+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
765+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
766+ * GNU General Public License for more details.
767+ *
768+ * You should have received a copy of the GNU General Public License
769+ * along with this program; if not, write to the Free Software
770+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
771+ *
772+ */
773+
774+#ifndef WL_GRAPHIC_TEXT_RENDERED_TEXT_H
775+#define WL_GRAPHIC_TEXT_RENDERED_TEXT_H
776+
777+#include <memory>
778+#include <vector>
779+
780+#include "base/rect.h"
781+#include "base/vector.h"
782+#include "graphic/image.h"
783+#include "graphic/rendertarget.h"
784+#include "graphic/texture.h"
785+
786+namespace UI {
787+
788+/// A rectangle that contains blitting information for rendered text.
789+class RenderedRect {
790+public:
791+ /// Whether the RenderedRect's image should be blitted once or tiled
792+ enum class DrawMode {
793+ kBlit, // The image texture is considered a foreground image and blitted as is
794+ kTile // The image texture is considered a background image and is tiled to fill the rect
795+ };
796+
797+private:
798+ // The image is managed by a transient cache
799+ RenderedRect(const Recti& init_rect,
800+ std::shared_ptr<const Image> init_image,
801+ bool visited,
802+ const RGBColor& color,
803+ bool is_background_color_set,
804+ DrawMode init_mode);
805+ // The image is managed by a pernament cache
806+ RenderedRect(const Recti& init_rect,
807+ const Image* init_image,
808+ bool visited,
809+ const RGBColor& color,
810+ bool is_background_color_set,
811+ DrawMode init_mode);
812+
813+public:
814+ /// RenderedRect will contain a background image that should be tiled
815+ RenderedRect(const Recti& init_rect, const Image* init_image);
816+
817+ /// RenderedRect will contain a background color that should be tiled
818+ RenderedRect(const Recti& init_rect, const RGBColor& color);
819+
820+ /// RenderedRect will contain a normal image that is managed by a transient cache.
821+ /// Use this if the image is managed by an instance of TextureCache.
822+ RenderedRect(std::shared_ptr<const Image> init_image);
823+
824+ /// RenderedRect will contain a normal image that is managed by a permanent cache.
825+ /// Use this if the image is managed by g_gr->images().
826+ RenderedRect(const Image* init_image);
827+ ~RenderedRect() {
828+ }
829+
830+ /// An image to be blitted. Can be nullptr.
831+ const Image* image() const;
832+
833+ /// The x position of the rectangle
834+ int x() const;
835+ /// The y position of the rectangle
836+ int y() const;
837+ /// The width of the rectangle
838+ int width() const;
839+ /// The height of the rectangle
840+ int height() const;
841+
842+ /// Change x and y position of the rectangle.
843+ void set_origin(const Vector2i& new_origin);
844+
845+ /// Set that this rectangle was already visited by the font renderer. Needed by the font renderer
846+ /// for correct positioning.
847+ void set_visited();
848+ /// Whether this rectangle was already visited by the font renderer
849+ bool was_visited() const;
850+
851+ /// Whether this rectangle contains a background color
852+ bool has_background_color() const;
853+ /// This rectangle's background color
854+ const RGBColor& background_color() const;
855+
856+ /// Whether the RenderedRect's image should be blitted once or tiled
857+ DrawMode mode() const;
858+
859+private:
860+ Recti rect_;
861+ // We have 2 image objects depending on the caching situation - only use one of them at the same
862+ // time.
863+ std::shared_ptr<const Image> transient_image_; // Shared ownership, managed by a transient cache
864+ const Image* permanent_image_; // Not owned, managed by a permanent cache
865+ bool visited_;
866+ const RGBColor background_color_;
867+ const bool is_background_color_set_;
868+ const DrawMode mode_;
869+};
870+
871+struct RenderedText {
872+ /// RenderedRects that can be drawn on screen
873+ std::vector<std::unique_ptr<RenderedRect>> rects;
874+
875+ /// The width occupied by all rects in pixels.
876+ int width() const;
877+ /// The height occupied by all rects in pixels.
878+ int height() const;
879+
880+ enum class CropMode {
881+ // The RenderTarget will handle all cropping. Use this for scrollable elements or when you
882+ // don't expect any cropping.
883+ kRenderTarget,
884+ // The draw() method will handle horizontal cropping. Use this for table entries.
885+ kSelf
886+ };
887+
888+ /// Draw the rects. 'position', 'region' and 'align' are used to control the overall drawing
889+ /// position and cropping.
890+ /// For 'cropmode', use kRenderTarget if you wish the text to fill the whole RenderTarget, e.g.
891+ /// for scrolling panels. Use kHorizontal for horizontal cropping in smaller elements, e.g. table
892+ /// cells.
893+ void draw(RenderTarget& dst,
894+ const Vector2i& position,
895+ const Recti& region,
896+ UI::Align align = UI::Align::kLeft,
897+ CropMode cropmode = CropMode::kRenderTarget) const;
898+
899+ /// Draw the rects without cropping. 'position' and 'align' are used to control the overall
900+ /// drawing position
901+ void draw(RenderTarget& dst, const Vector2i& position, UI::Align align = UI::Align::kLeft) const;
902+
903+private:
904+ /// Helper function for draw(). Blits the rect's background color and images. The rect will be
905+ /// positioned according to 'aligned_position' and cropped according to 'region'. 'offxet_x' and
906+ /// 'align' are used by the cropping algorithm when we use CropMode::kSelf mode.
907+ void blit_rect(RenderTarget& dst,
908+ int offset_x,
909+ const Vector2i& aligned_position,
910+ const RenderedRect& rect,
911+ const Recti& region,
912+ Align align,
913+ CropMode cropmode) const;
914+
915+ /// Helper function for CropMode::kSelf. It only does horizontal cropping since the RenderTarget
916+ /// itself still seems to take care of vertical stuff for us in tables.
917+ void blit_cropped(RenderTarget& dst,
918+ int offset_x,
919+ const Vector2i& position,
920+ const Vector2i& blit_point,
921+ const RenderedRect& rect,
922+ const Recti& region,
923+ Align align) const;
924+};
925+
926+} // namespace UI
927+
928+#endif // end of include guard: WL_GRAPHIC_TEXT_RENDERED_TEXT_H
929
930=== modified file 'src/graphic/text/rt_render.cc'
931--- src/graphic/text/rt_render.cc 2017-05-03 07:24:06 +0000
932+++ src/graphic/text/rt_render.cc 2017-05-23 21:33:09 +0000
933@@ -43,6 +43,7 @@
934 #include "graphic/text/bidi.h"
935 #include "graphic/text/font_io.h"
936 #include "graphic/text/font_set.h"
937+#include "graphic/text/rendered_text.h"
938 #include "graphic/text/rt_parse.h"
939 #include "graphic/text/sdl_ttf_font.h"
940 #include "graphic/text/textstream.h"
941@@ -52,7 +53,7 @@
942 #include "io/filesystem/layered_filesystem.h"
943
944 using namespace std;
945-
946+// TODO(GunChleoc): text line can start with space text node when it's within a div.
947 namespace RT {
948
949 static const uint16_t INFINITE_WIDTH = 65535; // 2^16-1
950@@ -227,15 +228,20 @@
951 virtual ~RenderNode() {
952 }
953
954- virtual uint16_t width() = 0;
955- virtual uint16_t height() = 0;
956- virtual uint16_t hotspot_y() = 0;
957- virtual Texture* render(TextureCache* texture_cache) = 0;
958-
959- virtual bool is_non_mandatory_space() {
960+ virtual uint16_t width() const = 0;
961+ virtual uint16_t height() const = 0;
962+ virtual uint16_t hotspot_y() const = 0;
963+ virtual UI::RenderedText* render(TextureCache* texture_cache) = 0;
964+
965+ // TODO(GunChleoc): Remove this function once conversion is finished and well tested.
966+ virtual std::string debug_info() const = 0;
967+
968+ // If a node is a non-mandatory space, it can be removed as a leading/trailing space
969+ // by the positioning algorithm.
970+ virtual bool is_non_mandatory_space() const {
971 return false;
972 }
973- virtual bool is_expanding() {
974+ virtual bool is_expanding() const {
975 return false;
976 }
977 virtual void set_w(uint16_t) {
978@@ -245,19 +251,19 @@
979 return vector<Reference>();
980 }
981
982- Floating get_floating() {
983+ Floating get_floating() const {
984 return floating_;
985 }
986 void set_floating(Floating f) {
987 floating_ = f;
988 }
989- UI::Align halign() {
990+ UI::Align halign() const {
991 return halign_;
992 }
993 void set_halign(UI::Align ghalign) {
994 halign_ = ghalign;
995 }
996- UI::Align valign() {
997+ UI::Align valign() const {
998 return valign_;
999 }
1000 void set_valign(UI::Align gvalign) {
1001@@ -269,13 +275,38 @@
1002 void set_y(int32_t ny) {
1003 y_ = ny;
1004 }
1005- int32_t x() {
1006+ int32_t x() const {
1007 return x_;
1008 }
1009- int32_t y() {
1010+ int32_t y() const {
1011 return y_;
1012 }
1013
1014+protected:
1015+ /// Throws a TextureTooBig exception if the given dimensions would be bigger than the graphics
1016+ /// can handle
1017+ void check_size(int check_w, int check_h) {
1018+// Test for minimum supported size in debug builds.
1019+#ifndef NDEBUG
1020+ const int maximum_size = kMinimumSizeForTextures;
1021+#else
1022+ const int maximum_size = g_gr->max_texture_size();
1023+#endif
1024+ if (check_w > maximum_size || check_h > maximum_size) {
1025+ const std::string error_message =
1026+ (boost::format("Texture (%d, %d) too big! Maximum size is %d.") % check_w % check_h %
1027+ maximum_size)
1028+ .str();
1029+ log("%s\n", error_message.c_str());
1030+ throw TextureTooBig(error_message);
1031+ }
1032+ }
1033+
1034+ /// Check the size for the node's own dimensions
1035+ void check_size() {
1036+ check_size(width(), height());
1037+ }
1038+
1039 private:
1040 Floating floating_;
1041 UI::Align halign_;
1042@@ -479,13 +510,17 @@
1043 virtual ~TextNode() {
1044 }
1045
1046- uint16_t width() override {
1047+ std::string debug_info() const override {
1048+ return "'" + txt_ + "'";
1049+ }
1050+
1051+ uint16_t width() const override {
1052 return w_;
1053 }
1054- uint16_t height() override {
1055+ uint16_t height() const override {
1056 return h_ + nodestyle_.spacing;
1057 }
1058- uint16_t hotspot_y() override;
1059+ uint16_t hotspot_y() const override;
1060 const vector<Reference> get_references() override {
1061 vector<Reference> rv;
1062 if (!nodestyle_.reference.empty()) {
1063@@ -495,7 +530,7 @@
1064 return rv;
1065 }
1066
1067- Texture* render(TextureCache* texture_cache) override;
1068+ UI::RenderedText* render(TextureCache* texture_cache) override;
1069
1070 protected:
1071 uint16_t w_, h_;
1072@@ -512,18 +547,20 @@
1073 fontcache_(font),
1074 font_(dynamic_cast<SdlTtfFont&>(fontcache_.get_font(&nodestyle_))) {
1075 font_.dimensions(txt_, ns.font_style, &w_, &h_);
1076+ check_size();
1077 }
1078-uint16_t TextNode::hotspot_y() {
1079+uint16_t TextNode::hotspot_y() const {
1080 return font_.ascent(nodestyle_.font_style);
1081 }
1082
1083-Texture* TextNode::render(TextureCache* texture_cache) {
1084- const Texture& img =
1085+UI::RenderedText* TextNode::render(TextureCache* texture_cache) {
1086+ auto rendered_image =
1087 font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
1088- Texture* rv = new Texture(img.width(), img.height());
1089- rv->blit(Rectf(0, 0, img.width(), img.height()), img, Rectf(0, 0, img.width(), img.height()), 1.,
1090- BlendMode::Copy);
1091- return rv;
1092+ assert(rendered_image.get() != nullptr);
1093+ UI::RenderedText* rendered_text = new UI::RenderedText();
1094+ rendered_text->rects.push_back(
1095+ std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
1096+ return rendered_text;
1097 }
1098
1099 /*
1100@@ -536,12 +573,18 @@
1101 FontCache& font, NodeStyle& ns, uint16_t w, const string& txt, bool expanding = false)
1102 : TextNode(font, ns, txt), is_expanding_(expanding) {
1103 w_ = w;
1104+ check_size();
1105 }
1106 virtual ~FillingTextNode() {
1107 }
1108- Texture* render(TextureCache*) override;
1109-
1110- bool is_expanding() override {
1111+
1112+ std::string debug_info() const override {
1113+ return "ft";
1114+ }
1115+
1116+ UI::RenderedText* render(TextureCache*) override;
1117+
1118+ bool is_expanding() const override {
1119 return is_expanding_;
1120 }
1121 void set_w(uint16_t w) override {
1122@@ -551,15 +594,29 @@
1123 private:
1124 bool is_expanding_;
1125 };
1126-Texture* FillingTextNode::render(TextureCache* texture_cache) {
1127- const Texture& t =
1128- font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
1129- Texture* rv = new Texture(w_, h_);
1130- for (uint16_t curx = 0; curx < w_; curx += t.width()) {
1131- Rectf srcrect(0.f, 0.f, min<int>(t.width(), w_ - curx), h_);
1132- rv->blit(Rectf(curx, 0, srcrect.w, srcrect.h), t, srcrect, 1., BlendMode::Copy);
1133+UI::RenderedText* FillingTextNode::render(TextureCache* texture_cache) {
1134+ UI::RenderedText* rendered_text = new UI::RenderedText();
1135+ const std::string hash =
1136+ (boost::format("rt:fill:%s:%s:%i:%i:%i:%s") % txt_ % nodestyle_.font_color.hex_value() %
1137+ nodestyle_.font_style % width() % height() % (is_expanding_ ? "e" : "f"))
1138+ .str();
1139+
1140+ std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
1141+ if (rendered_image.get() == nullptr) {
1142+ std::shared_ptr<const Image> ttf =
1143+ font_.render(txt_, nodestyle_.font_color, nodestyle_.font_style, texture_cache);
1144+ auto texture = std::make_shared<Texture>(width(), height());
1145+ for (uint16_t curx = 0; curx < w_; curx += ttf->width()) {
1146+ Rectf srcrect(0.f, 0.f, min<int>(ttf->width(), w_ - curx), h_);
1147+ texture->blit(
1148+ Rectf(curx, 0, srcrect.w, srcrect.h), *ttf.get(), srcrect, 1., BlendMode::Copy);
1149+ }
1150+ rendered_image = texture_cache->insert(hash, std::move(texture));
1151 }
1152- return rv;
1153+ assert(rendered_image.get() != nullptr);
1154+ rendered_text->rects.push_back(
1155+ std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
1156+ return rendered_text;
1157 }
1158
1159 /*
1160@@ -569,20 +626,34 @@
1161 class WordSpacerNode : public TextNode {
1162 public:
1163 WordSpacerNode(FontCache& font, NodeStyle& ns) : TextNode(font, ns, " ") {
1164+ check_size();
1165 }
1166 static void show_spaces(bool t) {
1167 show_spaces_ = t;
1168 }
1169
1170- Texture* render(TextureCache* texture_cache) override {
1171+ std::string debug_info() const override {
1172+ return "wsp";
1173+ }
1174+
1175+ UI::RenderedText* render(TextureCache* texture_cache) override {
1176 if (show_spaces_) {
1177- Texture* rv = new Texture(w_, h_);
1178- rv->fill_rect(Rectf(0, 0, w_, h_), RGBAColor(0xcc, 0, 0, 0xcc));
1179- return rv;
1180+ UI::RenderedText* rendered_text = new UI::RenderedText();
1181+ const std::string hash = (boost::format("rt:wsp:%i:%i") % width() % height()).str();
1182+ std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
1183+ if (rendered_image.get() == nullptr) {
1184+ auto texture = std::make_shared<Texture>(width(), height());
1185+ texture->fill_rect(Rectf(0.f, 0.f, w_, h_), RGBAColor(0xcc, 0, 0, 0xcc));
1186+ rendered_image = texture_cache->insert(hash, std::move(texture));
1187+ }
1188+ assert(rendered_image.get() != nullptr);
1189+ rendered_text->rects.push_back(
1190+ std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
1191+ return rendered_text;
1192 }
1193 return TextNode::render(texture_cache);
1194 }
1195- bool is_non_mandatory_space() override {
1196+ bool is_non_mandatory_space() const override {
1197 return true;
1198 }
1199
1200@@ -599,19 +670,24 @@
1201 public:
1202 NewlineNode(NodeStyle& ns) : RenderNode(ns) {
1203 }
1204- uint16_t height() override {
1205+
1206+ std::string debug_info() const override {
1207+ return "nl";
1208+ }
1209+
1210+ uint16_t height() const override {
1211 return 0;
1212 }
1213- uint16_t width() override {
1214+ uint16_t width() const override {
1215 return INFINITE_WIDTH;
1216 }
1217- uint16_t hotspot_y() override {
1218+ uint16_t hotspot_y() const override {
1219 return 0;
1220 }
1221- Texture* render(TextureCache* /* texture_cache */) override {
1222+ UI::RenderedText* render(TextureCache* /* texture_cache */) override {
1223 NEVER_HERE();
1224 }
1225- bool is_non_mandatory_space() override {
1226+ bool is_non_mandatory_space() const override {
1227 return true;
1228 }
1229 };
1230@@ -622,52 +698,76 @@
1231 class SpaceNode : public RenderNode {
1232 public:
1233 SpaceNode(NodeStyle& ns, uint16_t w, uint16_t h = 0, bool expanding = false)
1234- : RenderNode(ns), w_(w), h_(h), background_image_(nullptr), is_expanding_(expanding) {
1235- }
1236-
1237- uint16_t height() override {
1238+ : RenderNode(ns),
1239+ w_(w),
1240+ h_(h),
1241+ background_image_(nullptr),
1242+ filename_(""),
1243+ is_expanding_(expanding) {
1244+ check_size();
1245+ }
1246+
1247+ std::string debug_info() const override {
1248+ return "sp";
1249+ }
1250+
1251+ uint16_t height() const override {
1252 return h_;
1253 }
1254- uint16_t width() override {
1255+ uint16_t width() const override {
1256 return w_;
1257 }
1258- uint16_t hotspot_y() override {
1259+ uint16_t hotspot_y() const override {
1260 return h_;
1261 }
1262- Texture* render(TextureCache* /* texture_cache */) override {
1263- Texture* rv = new Texture(w_, h_);
1264+ UI::RenderedText* render(TextureCache* texture_cache) override {
1265+ UI::RenderedText* rendered_text = new UI::RenderedText();
1266+ const std::string hash = (boost::format("rt:sp:%s:%i:%i:%s") % filename_ % width() %
1267+ height() % (is_expanding_ ? "e" : "f"))
1268+ .str();
1269
1270- // Draw background image (tiling)
1271- if (background_image_) {
1272- Rectf dst;
1273- Rectf srcrect(0, 0, 1, 1);
1274- for (uint16_t curx = 0; curx < w_; curx += background_image_->width()) {
1275- dst.x = curx;
1276- dst.y = 0;
1277- srcrect.w = dst.w = min<int>(background_image_->width(), w_ - curx);
1278- srcrect.h = dst.h = h_;
1279- rv->blit(dst, *background_image_, srcrect, 1., BlendMode::Copy);
1280+ std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
1281+ if (rendered_image.get() == nullptr) {
1282+ // Draw background image (tiling)
1283+ auto texture = std::make_shared<Texture>(width(), height());
1284+ if (background_image_ != nullptr) {
1285+ Rectf dst;
1286+ Rectf srcrect(0, 0, 1, 1);
1287+ for (uint16_t curx = 0; curx < w_; curx += background_image_->width()) {
1288+ dst.x = curx;
1289+ dst.y = 0;
1290+ srcrect.w = dst.w = min<int>(background_image_->width(), w_ - curx);
1291+ srcrect.h = dst.h = h_;
1292+ texture->blit(dst, *background_image_, srcrect, 1., BlendMode::Copy);
1293+ }
1294+ } else {
1295+ texture->fill_rect(Rectf(0.f, 0.f, w_, h_), RGBAColor(255, 255, 255, 0));
1296 }
1297- } else {
1298- rv->fill_rect(Rectf(0, 0, w_, h_), RGBAColor(255, 255, 255, 0));
1299+ rendered_image = texture_cache->insert(hash, std::move(texture));
1300 }
1301- return rv;
1302+ assert(rendered_image.get() != nullptr);
1303+ rendered_text->rects.push_back(
1304+ std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
1305+ return rendered_text;
1306 }
1307- bool is_expanding() override {
1308+
1309+ bool is_expanding() const override {
1310 return is_expanding_;
1311 }
1312 void set_w(uint16_t w) override {
1313 w_ = w;
1314 }
1315
1316- void set_background(const Image* s) {
1317+ void set_background(const Image* s, const std::string& filename) {
1318 background_image_ = s;
1319+ filename_ = filename;
1320 h_ = s->height();
1321 }
1322
1323 private:
1324 uint16_t w_, h_;
1325 const Image* background_image_; // not owned
1326+ std::string filename_;
1327 bool is_expanding_;
1328 };
1329
1330@@ -690,13 +790,17 @@
1331 nodes_to_render_.clear();
1332 }
1333
1334- uint16_t width() override {
1335+ std::string debug_info() const override {
1336+ return "div";
1337+ }
1338+
1339+ uint16_t width() const override {
1340 return w_ + margin_.left + margin_.right;
1341 }
1342- uint16_t height() override {
1343+ uint16_t height() const override {
1344 return h_ + margin_.top + margin_.bottom;
1345 }
1346- uint16_t hotspot_y() override {
1347+ uint16_t hotspot_y() const override {
1348 return height();
1349 }
1350
1351@@ -704,60 +808,48 @@
1352 return desired_width_;
1353 }
1354
1355- Texture* render(TextureCache* texture_cache) override {
1356- if (width() > g_gr->max_texture_size() || height() > g_gr->max_texture_size()) {
1357- const std::string error_message =
1358- (boost::format("Texture (%d, %d) too big! Maximum size is %d.") % width() % height() %
1359- g_gr->max_texture_size())
1360- .str();
1361- log("%s\n", error_message.c_str());
1362- throw TextureTooBig(error_message);
1363- }
1364- Texture* rv = new Texture(width(), height());
1365- rv->fill_rect(Rectf(0, 0, rv->width(), rv->height()), RGBAColor(255, 255, 255, 0));
1366+ UI::RenderedText* render(TextureCache* texture_cache) override {
1367+ UI::RenderedText* rendered_text = new UI::RenderedText();
1368+ // Preserve padding
1369+ rendered_text->rects.push_back(std::unique_ptr<UI::RenderedRect>(
1370+ new UI::RenderedRect(Recti(0, 0, width(), height()), nullptr)));
1371
1372 // Draw Solid background Color
1373- bool set_alpha = true;
1374 if (is_background_color_set_) {
1375- rv->fill_rect(Rectf(margin_.left, margin_.top, w_, h_), background_color_);
1376- set_alpha = false;
1377+ UI::RenderedRect* bg_rect =
1378+ new UI::RenderedRect(Recti(margin_.left, margin_.top, w_, h_), background_color_);
1379+ // Size is automatically adjusted in RenderedText while blitting, so no need to call
1380+ // check_size() here.
1381+ rendered_text->rects.push_back(std::unique_ptr<UI::RenderedRect>(std::move(bg_rect)));
1382 }
1383
1384 // Draw background image (tiling)
1385- if (background_image_) {
1386- Rectf dst;
1387- Rectf src(0, 0, 0, 0);
1388-
1389- for (uint16_t cury = margin_.top; cury < h_ + margin_.top;
1390- cury += background_image_->height()) {
1391- for (uint16_t curx = margin_.left; curx < w_ + margin_.left;
1392- curx += background_image_->width()) {
1393- dst.x = curx;
1394- dst.y = cury;
1395- src.w = dst.w = min<int>(background_image_->width(), w_ + margin_.left - curx);
1396- src.h = dst.h = min<int>(background_image_->height(), h_ + margin_.top - cury);
1397- rv->blit(dst, *background_image_, src, 1., BlendMode::Copy);
1398+ if (background_image_ != nullptr) {
1399+ UI::RenderedRect* bg_rect =
1400+ new UI::RenderedRect(Recti(margin_.left, margin_.top, w_, h_), background_image_);
1401+ check_size(bg_rect->width(), bg_rect->height());
1402+ rendered_text->rects.push_back(std::unique_ptr<UI::RenderedRect>(std::move(bg_rect)));
1403+ }
1404+
1405+ for (RenderNode* n : nodes_to_render_) {
1406+ const auto& renderme = n->render(texture_cache);
1407+ for (auto& rendered_rect : renderme->rects) {
1408+ if (rendered_rect->was_visited()) {
1409+ rendered_rect->set_origin(
1410+ Vector2i(x() + rendered_rect->x(), y() + rendered_rect->y() + margin_.top));
1411+
1412+ } else {
1413+ rendered_rect->set_origin(
1414+ Vector2i(x() + n->x() + margin_.left, y() + n->y() + margin_.top));
1415+ rendered_rect->set_visited();
1416 }
1417- }
1418- set_alpha = false;
1419- }
1420-
1421- for (RenderNode* n : nodes_to_render_) {
1422- Texture* node_texture = n->render(texture_cache);
1423- if (node_texture) {
1424- Rectf dst(n->x() + margin_.left, n->y() + margin_.top, node_texture->width(),
1425- node_texture->height());
1426- Rectf src(0, 0, node_texture->width(), node_texture->height());
1427- rv->blit(
1428- dst, *node_texture, src, 1., set_alpha ? BlendMode::Copy : BlendMode::UseAlpha);
1429- delete node_texture;
1430+ rendered_text->rects.push_back(std::move(rendered_rect));
1431 }
1432 delete n;
1433 }
1434-
1435 nodes_to_render_.clear();
1436
1437- return rv;
1438+ return rendered_text;
1439 }
1440 const vector<Reference> get_references() override {
1441 return refs_;
1442@@ -806,30 +898,62 @@
1443 : RenderNode(ns),
1444 image_(use_playercolor ? playercolor_image(color, image_filename) :
1445 g_gr->images().get(image_filename)),
1446- scale_(scale) {
1447- }
1448-
1449- uint16_t width() override {
1450+ filename_(image_filename),
1451+ scale_(scale),
1452+ color_(color),
1453+ use_playercolor_(use_playercolor) {
1454+ check_size();
1455+ }
1456+
1457+ std::string debug_info() const override {
1458+ return "img";
1459+ }
1460+
1461+ uint16_t width() const override {
1462 return scale_ * image_->width();
1463 }
1464- uint16_t height() override {
1465- return scale_ * image_->height();
1466- }
1467- uint16_t hotspot_y() override {
1468- return scale_ * image_->height();
1469- }
1470- Texture* render(TextureCache* texture_cache) override;
1471+ uint16_t height() const override {
1472+ return scale_ * image_->height();
1473+ }
1474+ uint16_t hotspot_y() const override {
1475+ return scale_ * image_->height();
1476+ }
1477+ UI::RenderedText* render(TextureCache* texture_cache) override;
1478
1479 private:
1480 const Image* image_;
1481+ const std::string filename_;
1482 const double scale_;
1483+ const RGBColor& color_;
1484+ bool use_playercolor_;
1485 };
1486
1487-Texture* ImgRenderNode::render(TextureCache* /* texture_cache */) {
1488- Texture* rv = new Texture(width(), height());
1489- rv->blit(Rectf(0, 0, width(), height()), *image_, Rectf(0, 0, image_->width(), image_->height()),
1490- 1., BlendMode::Copy);
1491- return rv;
1492+UI::RenderedText* ImgRenderNode::render(TextureCache* texture_cache) {
1493+ UI::RenderedText* rendered_text = new UI::RenderedText();
1494+
1495+ if (scale_ == 1.0) {
1496+ // Image can be used as is, and has already been cached in g_gr->images()
1497+ assert(image_ != nullptr);
1498+ rendered_text->rects.push_back(
1499+ std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(image_)));
1500+ } else {
1501+ const std::string hash = (boost::format("rt:img:%s:%s:%i:%i") % filename_ %
1502+ (use_playercolor_ ? color_.hex_value() : "") % width() % height())
1503+ .str();
1504+ std::shared_ptr<const Image> rendered_image = texture_cache->get(hash);
1505+ if (rendered_image.get() == nullptr) {
1506+ auto texture = std::make_shared<Texture>(width(), height());
1507+ texture->blit(Rectf(0.f, 0.f, width(), height()), *image_,
1508+ Rectf(0.f, 0.f, image_->width(), image_->height()), 1., BlendMode::Copy);
1509+ rendered_image = texture_cache->insert(hash, std::move(texture));
1510+ }
1511+
1512+ assert(rendered_image.get() != nullptr);
1513+ rendered_text->rects.push_back(
1514+ std::unique_ptr<UI::RenderedRect>(new UI::RenderedRect(rendered_image)));
1515+ }
1516+
1517+ return rendered_text;
1518 }
1519 // End: Helper Stuff
1520
1521@@ -1136,6 +1260,7 @@
1522 const UI::FontSets& fontsets)
1523 : TagHandler(tag, fc, ns, image_cache, init_renderer_style, fontsets),
1524 background_image_(nullptr),
1525+ image_filename_(""),
1526 space_(0) {
1527 }
1528
1529@@ -1151,6 +1276,7 @@
1530 fill_text_ = a["fill"].get_string();
1531 try {
1532 background_image_ = image_cache_->get(fill_text_);
1533+ image_filename_ = fill_text_;
1534 fill_text_ = "";
1535 } catch (ImageNotFound&) {
1536 }
1537@@ -1172,7 +1298,7 @@
1538 sn = new SpaceNode(nodestyle_, 0, 0, true);
1539
1540 if (background_image_)
1541- sn->set_background(background_image_);
1542+ sn->set_background(background_image_, image_filename_);
1543 rn = sn;
1544 }
1545 nodes.push_back(rn);
1546@@ -1181,6 +1307,7 @@
1547 private:
1548 string fill_text_;
1549 const Image* background_image_;
1550+ std::string image_filename_;
1551 uint16_t space_;
1552 };
1553
1554@@ -1481,10 +1608,11 @@
1555 return nodes[0];
1556 }
1557
1558-Texture* Renderer::render(const string& text, uint16_t width, const TagSet& allowed_tags) {
1559+std::shared_ptr<const UI::RenderedText>
1560+Renderer::render(const string& text, uint16_t width, const TagSet& allowed_tags) {
1561 std::unique_ptr<RenderNode> node(layout_(text, width, allowed_tags));
1562
1563- return node->render(texture_cache_);
1564+ return std::shared_ptr<const UI::RenderedText>(std::move(node->render(texture_cache_)));
1565 }
1566
1567 IRefMap*
1568
1569=== modified file 'src/graphic/text/rt_render.h'
1570--- src/graphic/text/rt_render.h 2017-01-25 18:55:59 +0000
1571+++ src/graphic/text/rt_render.h 2017-05-23 21:33:09 +0000
1572@@ -28,11 +28,10 @@
1573
1574 #include "graphic/color.h"
1575 #include "graphic/image.h"
1576+#include "graphic/image_cache.h"
1577 #include "graphic/text/font_set.h"
1578-
1579-class Texture;
1580-class ImageCache;
1581-class TextureCache;
1582+#include "graphic/text/rendered_text.h"
1583+#include "graphic/text/texture_cache.h"
1584
1585 namespace RT {
1586
1587@@ -80,9 +79,9 @@
1588 ~Renderer();
1589
1590 // Render the given string in the given width. Restricts the allowed tags to
1591- // the ones in TagSet. The renderer does not do caching in the TextureCache
1592- // for its individual nodes, but the font render does.
1593- Texture* render(const std::string&, uint16_t width, const TagSet& tagset = TagSet());
1594+ // the ones in TagSet.
1595+ std::shared_ptr<const UI::RenderedText>
1596+ render(const std::string&, uint16_t width, const TagSet& tagset = TagSet());
1597
1598 // Returns a reference map of the clickable hyperlinks in the image. This
1599 // will do no caching and needs to do all layouting, so do not call this too
1600
1601=== modified file 'src/graphic/text/sdl_ttf_font.cc'
1602--- src/graphic/text/sdl_ttf_font.cc 2017-01-25 18:55:59 +0000
1603+++ src/graphic/text/sdl_ttf_font.cc 2017-05-23 21:33:09 +0000
1604@@ -63,17 +63,18 @@
1605 *gh = h;
1606 }
1607
1608-const Texture& SdlTtfFont::render(const std::string& txt,
1609- const RGBColor& clr,
1610- int style,
1611- TextureCache* texture_cache) {
1612+std::shared_ptr<const Image> SdlTtfFont::render(const std::string& txt,
1613+ const RGBColor& clr,
1614+ int style,
1615+ TextureCache* texture_cache) {
1616 const std::string hash =
1617- (boost::format("%s:%s:%i:%02x%02x%02x:%i") % font_name_ % ptsize_ % txt %
1618+ (boost::format("ttf:%s:%s:%i:%02x%02x%02x:%i") % font_name_ % ptsize_ % txt %
1619 static_cast<int>(clr.r) % static_cast<int>(clr.g) % static_cast<int>(clr.b) % style)
1620 .str();
1621- const Texture* rv = texture_cache->get(hash);
1622- if (rv)
1623- return *rv;
1624+ std::shared_ptr<const Image> rv = texture_cache->get(hash);
1625+ if (rv.get() != nullptr) {
1626+ return rv;
1627+ }
1628
1629 set_style(style);
1630
1631@@ -128,7 +129,7 @@
1632 throw RenderError(
1633 (boost::format("Rendering '%s' gave the error: %s") % txt % TTF_GetError()).str());
1634
1635- return *texture_cache->insert(hash, std::unique_ptr<Texture>(new Texture(text_surface)));
1636+ return texture_cache->insert(hash, std::make_shared<Texture>(text_surface));
1637 }
1638
1639 uint16_t SdlTtfFont::ascent(int style) const {
1640
1641=== modified file 'src/graphic/text/sdl_ttf_font.h'
1642--- src/graphic/text/sdl_ttf_font.h 2017-02-28 20:07:07 +0000
1643+++ src/graphic/text/sdl_ttf_font.h 2017-05-23 21:33:09 +0000
1644@@ -25,8 +25,8 @@
1645
1646 #include <SDL_ttf.h>
1647
1648+#include "graphic/text/texture_cache.h"
1649 #include "graphic/texture.h"
1650-#include "graphic/texture_cache.h"
1651
1652 namespace RT {
1653
1654@@ -51,7 +51,8 @@
1655 }
1656
1657 virtual void dimensions(const std::string&, int, uint16_t*, uint16_t*) = 0;
1658- virtual const Texture& render(const std::string&, const RGBColor& clr, int, TextureCache*) = 0;
1659+ virtual std::shared_ptr<const Image>
1660+ render(const std::string&, const RGBColor& clr, int, TextureCache*) = 0;
1661
1662 virtual uint16_t ascent(int) const = 0;
1663 virtual TTF_Font* get_ttf_font() const = 0;
1664@@ -64,7 +65,8 @@
1665 virtual ~SdlTtfFont();
1666
1667 void dimensions(const std::string&, int, uint16_t* w, uint16_t* h) override;
1668- const Texture& render(const std::string&, const RGBColor& clr, int, TextureCache*) override;
1669+ std::shared_ptr<const Image>
1670+ render(const std::string&, const RGBColor& clr, int, TextureCache*) override;
1671 uint16_t ascent(int) const override;
1672 TTF_Font* get_ttf_font() const override {
1673 return font_;
1674
1675=== modified file 'src/graphic/text/test/CMakeLists.txt'
1676--- src/graphic/text/test/CMakeLists.txt 2015-10-15 10:46:37 +0000
1677+++ src/graphic/text/test/CMakeLists.txt 2017-05-23 21:33:09 +0000
1678@@ -23,7 +23,6 @@
1679 DEPENDS
1680 base_i18n
1681 graphic_image_cache
1682- graphic_surface
1683 graphic_text
1684 io_filesystem
1685 )
1686
1687=== modified file 'src/graphic/text/test/render.cc'
1688--- src/graphic/text/test/render.cc 2017-01-25 18:55:59 +0000
1689+++ src/graphic/text/test/render.cc 2017-05-23 21:33:09 +0000
1690@@ -28,7 +28,7 @@
1691 #include "graphic/image_cache.h"
1692 #include "graphic/text/rt_render.h"
1693 #include "graphic/text/test/paths.h"
1694-#include "graphic/texture_cache.h"
1695+#include "graphic/text/texture_cache.h"
1696 #include "io/filesystem/layered_filesystem.h"
1697
1698 StandaloneRenderer::StandaloneRenderer() {
1699
1700=== modified file 'src/graphic/text/test/render_richtext.cc'
1701--- src/graphic/text/test/render_richtext.cc 2017-01-25 18:55:59 +0000
1702+++ src/graphic/text/test/render_richtext.cc 2017-05-23 21:33:09 +0000
1703@@ -33,6 +33,7 @@
1704 #include "config.h"
1705 #include "graphic/graphic.h"
1706 #include "graphic/image_io.h"
1707+#include "graphic/rendertarget.h"
1708 #include "graphic/text/rt_errors.h"
1709 #include "graphic/text/test/render.h"
1710 #include "graphic/texture.h"
1711@@ -138,11 +139,15 @@
1712 StandaloneRenderer standalone_renderer;
1713
1714 try {
1715- std::unique_ptr<Texture> texture(
1716- standalone_renderer.renderer()->render(txt, w, allowed_tags));
1717+ std::shared_ptr<const UI::RenderedText> rendered_text =
1718+ standalone_renderer.renderer()->render(txt, w, allowed_tags);
1719+ std::unique_ptr<Texture> texture(new Texture(rendered_text->width(), rendered_text->height()));
1720+ std::unique_ptr<RenderTarget> dst(new RenderTarget(texture.get()));
1721+ rendered_text->draw(*dst.get(), Vector2i::zero());
1722
1723 std::unique_ptr<FileSystem> fs(&FileSystem::create("."));
1724 std::unique_ptr<StreamWrite> sw(fs->open_stream_write(outname));
1725+
1726 if (!save_to_png(texture.get(), sw.get(), ColorType::RGBA)) {
1727 std::cout << "Could not encode PNG." << std::endl;
1728 }
1729
1730=== added file 'src/graphic/text/texture_cache.h'
1731--- src/graphic/text/texture_cache.h 1970-01-01 00:00:00 +0000
1732+++ src/graphic/text/texture_cache.h 2017-05-23 21:33:09 +0000
1733@@ -0,0 +1,39 @@
1734+/*
1735+ * Copyright (C) 2017 by the Widelands Development Team
1736+ *
1737+ * This program is free software; you can redistribute it and/or
1738+ * modify it under the terms of the GNU General Public License
1739+ * as published by the Free Software Foundation; either version 2
1740+ * of the License, or (at your option) any later version.
1741+ *
1742+ * This program is distributed in the hope that it will be useful,
1743+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1744+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1745+ * GNU General Public License for more details.
1746+ *
1747+ * You should have received a copy of the GNU General Public License
1748+ * along with this program; if not, write to the Free Software
1749+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
1750+ *
1751+ */
1752+
1753+#ifndef WL_GRAPHIC_TEXT_TEXTURE_CACHE_H
1754+#define WL_GRAPHIC_TEXT_TEXTURE_CACHE_H
1755+
1756+#include <memory>
1757+
1758+#include "graphic/image.h"
1759+#include "graphic/text/transient_cache.h"
1760+
1761+class TextureCache : public TransientCache<Image> {
1762+public:
1763+ TextureCache(uint32_t max_size_in_bytes) : TransientCache<Image>(max_size_in_bytes) {
1764+ }
1765+
1766+ std::shared_ptr<const Image> insert(const std::string& hash,
1767+ std::shared_ptr<const Image> entry) override {
1768+ return TransientCache<Image>::insert(hash, entry, entry->width() * entry->height() * 4);
1769+ }
1770+};
1771+
1772+#endif // end of include guard: WL_GRAPHIC_TEXT_TEXTURE_CACHE_H
1773
1774=== renamed file 'src/graphic/texture_cache.h' => 'src/graphic/text/transient_cache.h'
1775--- src/graphic/texture_cache.h 2017-01-25 18:55:59 +0000
1776+++ src/graphic/text/transient_cache.h 2017-05-23 21:33:09 +0000
1777@@ -17,8 +17,8 @@
1778 *
1779 */
1780
1781-#ifndef WL_GRAPHIC_TEXTURE_CACHE_H
1782-#define WL_GRAPHIC_TEXTURE_CACHE_H
1783+#ifndef WL_GRAPHIC_TEXT_TRANSIENT_CACHE_H
1784+#define WL_GRAPHIC_TEXT_TRANSIENT_CACHE_H
1785
1786 #include <cassert>
1787 #include <list>
1788@@ -26,55 +26,132 @@
1789 #include <memory>
1790 #include <string>
1791
1792+#include <SDL.h>
1793 #include <boost/utility.hpp>
1794
1795 #include "base/macros.h"
1796
1797-class Texture;
1798+// The implementation took inspiration from
1799+// https://timday.bitbucket.io/lru.html, but our use case here is a little
1800+// different.
1801
1802-// Caches transient Surfaces, i.e. those that are always free to be deleted
1803-// because they can be regenerated - somebody else must then recreate them when
1804-// they are needed again.
1805-//
1806-// Nothing in Widelands should hold onto a Surface they get from this class,
1807-// instead, they should use it only temporarily and rerequest it whenever they
1808-// need it.
1809-class TextureCache {
1810+/// Caches transient rendered text. The entries will be kept until the memory limit is reached,
1811+/// then the stalest entries will be deleted to make room for new entries.
1812+///
1813+/// We use shared_ptr so that other objects can hold on to the textures if they need them more
1814+/// permanently.
1815+template <typename T> class TransientCache {
1816 public:
1817- // Create a new Cache whichs combined pixels data in all transient surfaces
1818- // are always below the 'max_size_in_bytes'.
1819- TextureCache(uint32_t max_size_in_bytes);
1820- ~TextureCache();
1821+ /// Create a new cache in which the combined data size for all transient entries is always below
1822+ /// the 'max_size_in_arbitrary_unit'.
1823+ TransientCache(uint32_t max_size_in_arbitrary_unit);
1824+ virtual ~TransientCache();
1825
1826- /// Deletes all surfaces in the cache leaving it as if it were just created.
1827+ /// Deletes all entries in the cache, leaving it as if it were just created.
1828 void flush();
1829
1830 /// Returns an entry if it is cached, nullptr otherwise.
1831- Texture* get(const std::string& hash);
1832-
1833- // Inserts this entry into the TextureCache. asserts() that there is no
1834- // entry with this hash already cached. Returns the given Surface for
1835- // convenience. If 'transient' is false, this surface will not be deleted
1836- // automatically - use this if surfaces are around for a long time and
1837- // recreation is expensive (i.e. images loaded from disk).
1838- Texture* insert(const std::string& hash, std::unique_ptr<Texture> texture);
1839+ std::shared_ptr<const T> get(const std::string& hash);
1840+
1841+
1842+ /// Inserts this entry of type T into the cache. Returns the given T for convenience.
1843+ /// When overriding this function, calculate the size of 'entry' and then call
1844+ /// insert(hash, entry, entry_size_in_size_unit).
1845+ virtual std::shared_ptr<const T> insert(const std::string& hash,
1846+ std::shared_ptr<const T> entry) = 0;
1847+
1848+protected:
1849+ /// Inserts this entry of type T into the cache. asserts() that there is no entry with this hash
1850+ /// already cached. Returns the given T for convenience.
1851+ std::shared_ptr<const T> insert(const std::string& hash,
1852+ std::shared_ptr<const T> entry,
1853+ uint32_t entry_size_in_size_unit);
1854
1855 private:
1856+ /// Drop the oldest entry
1857 void drop();
1858
1859 using AccessHistory = std::list<std::string>;
1860 struct Entry {
1861- std::unique_ptr<Texture> texture;
1862+ std::shared_ptr<const T> entry;
1863+ uint32_t size;
1864 uint32_t last_access; // Mainly for debugging and analysis.
1865 const AccessHistory::iterator list_iterator;
1866 };
1867
1868- uint32_t max_size_in_bytes_;
1869- uint32_t size_in_bytes_;
1870+ uint32_t max_size_in_size_unit_;
1871+ uint32_t size_in_size_unit_;
1872 std::map<std::string, Entry> entries_;
1873 AccessHistory access_history_;
1874
1875- DISALLOW_COPY_AND_ASSIGN(TextureCache);
1876+ DISALLOW_COPY_AND_ASSIGN(TransientCache);
1877 };
1878
1879-#endif // end of include guard: WL_GRAPHIC_TEXTURE_CACHE_H
1880+// Implementation
1881+
1882+template <typename T>
1883+TransientCache<T>::TransientCache(uint32_t max_size_in_arbitrary_unit)
1884+ : max_size_in_size_unit_(max_size_in_arbitrary_unit), size_in_size_unit_(0) {
1885+}
1886+template <typename T> TransientCache<T>::~TransientCache() {
1887+ flush();
1888+}
1889+
1890+template <typename T> void TransientCache<T>::flush() {
1891+ access_history_.clear();
1892+ size_in_size_unit_ = 0;
1893+ entries_.clear();
1894+}
1895+
1896+/// Returns an entry if it is cached, nullptr otherwise.
1897+template <typename T> std::shared_ptr<const T> TransientCache<T>::get(const std::string& hash) {
1898+ const auto it = entries_.find(hash);
1899+ if (it == entries_.end()) {
1900+ return std::shared_ptr<const T>(nullptr);
1901+ }
1902+
1903+ // Move this to the back of the access list to signal that we have used this
1904+ // recently and update last access time.
1905+ access_history_.splice(access_history_.end(), access_history_, it->second.list_iterator);
1906+ it->second.last_access = SDL_GetTicks();
1907+ return it->second.entry;
1908+}
1909+
1910+template <typename T>
1911+std::shared_ptr<const T> TransientCache<T>::insert(const std::string& hash,
1912+ std::shared_ptr<const T> entry,
1913+ uint32_t entry_size_in_size_unit) {
1914+ assert(entries_.find(hash) == entries_.end());
1915+
1916+ while (!entries_.empty() &&
1917+ size_in_size_unit_ + entry_size_in_size_unit > max_size_in_size_unit_) {
1918+ drop();
1919+ }
1920+
1921+ // Record hash as most-recently-used.
1922+ AccessHistory::iterator it = access_history_.insert(access_history_.end(), hash);
1923+ size_in_size_unit_ += entry_size_in_size_unit;
1924+ return entries_
1925+ .insert(make_pair(hash, Entry{std::move(entry), entry_size_in_size_unit, SDL_GetTicks(), it}))
1926+ .first->second.entry;
1927+}
1928+
1929+template <typename T> void TransientCache<T>::drop() {
1930+ assert(!access_history_.empty());
1931+
1932+ // Identify least recently used key
1933+ const auto it = entries_.find(access_history_.front());
1934+ assert(it != entries_.end());
1935+
1936+ size_in_size_unit_ -= it->second.size;
1937+ // TODO(GunChleoc): Remove the following line once everything is converted to the new font
1938+ // renderer and all testing has been done.
1939+ // log("TransientCache: Dropping %d bytes, new size %d. Hash: %s\n", it->second.size,
1940+ // size_in_size_unit_, it->first.c_str());
1941+
1942+ // Erase both elements to completely purge record
1943+ entries_.erase(it);
1944+ access_history_.pop_front();
1945+}
1946+
1947+#endif // end of include guard: WL_GRAPHIC_TEXT_TRANSIENT_CACHE_H
1948
1949=== modified file 'src/graphic/text_layout.cc'
1950--- src/graphic/text_layout.cc 2017-05-13 13:14:29 +0000
1951+++ src/graphic/text_layout.cc 2017-05-23 21:33:09 +0000
1952@@ -48,14 +48,15 @@
1953 boost::replace_all(*text, "&amp;", "&"); // Must be performed last
1954 }
1955
1956-uint32_t text_width(const std::string& text, int ptsize) {
1957+int text_width(const std::string& text, int ptsize) {
1958 return UI::g_fh1->render(as_editorfont(text, ptsize - UI::g_fh1->fontset()->size_offset()))
1959 ->width();
1960 }
1961
1962-uint32_t text_height(const std::string& text, int ptsize) {
1963- return UI::g_fh1->render(as_editorfont(text.empty() ? "." : text,
1964- ptsize - UI::g_fh1->fontset()->size_offset()))
1965+int text_height(int ptsize, UI::FontSet::Face face) {
1966+ return UI::g_fh1
1967+ ->render(as_aligned(UI::g_fh1->fontset()->representative_character(), UI::Align::kLeft,
1968+ ptsize - UI::g_fh1->fontset()->size_offset(), RGBColor(0, 0, 0), face))
1969 ->height();
1970 }
1971
1972@@ -162,8 +163,10 @@
1973 .str());
1974 }
1975
1976-const Image* autofit_ui_text(const std::string& text, int width, RGBColor color, int fontsize) {
1977- const Image* result = UI::g_fh1->render(as_uifont(richtext_escape(text), fontsize, color));
1978+std::shared_ptr<const UI::RenderedText>
1979+autofit_ui_text(const std::string& text, int width, RGBColor color, int fontsize) {
1980+ std::shared_ptr<const UI::RenderedText> result =
1981+ UI::g_fh1->render(as_uifont(richtext_escape(text), fontsize, color));
1982 if (width > 0) { // Autofit
1983 for (; result->width() > width && fontsize >= kMinimumFontSize; --fontsize) {
1984 result = UI::g_fh1->render(
1985@@ -179,9 +182,14 @@
1986 * This mirrors the horizontal alignment for RTL languages.
1987 *
1988 * Do not store this value as it is based on the global font setting.
1989+ *
1990+ * If 'checkme' is not empty, mirror the alignment if the first 20 characters contain an RTL
1991+ * character. Otherwise, mirror if the current fontset is RTL.
1992 */
1993-Align mirror_alignment(Align alignment) {
1994- if (UI::g_fh1->fontset()->is_rtl()) {
1995+Align mirror_alignment(Align alignment, const std::string& checkme) {
1996+ bool do_swap_alignment = checkme.empty() ? UI::g_fh1->fontset()->is_rtl() :
1997+ i18n::has_rtl_character(checkme.c_str(), 20);
1998+ if (do_swap_alignment) {
1999 switch (alignment) {
2000 case Align::kLeft:
2001 alignment = Align::kRight;
2002
2003=== modified file 'src/graphic/text_layout.h'
2004--- src/graphic/text_layout.h 2017-05-13 13:14:29 +0000
2005+++ src/graphic/text_layout.h 2017-05-23 21:33:09 +0000
2006@@ -24,6 +24,7 @@
2007
2008 #include "graphic/align.h"
2009 #include "graphic/color.h"
2010+#include "graphic/font_handler1.h"
2011 #include "graphic/image.h"
2012 #include "graphic/text/font_set.h"
2013 #include "graphic/text_constants.h"
2014@@ -38,14 +39,13 @@
2015 * Returns the exact width of the text rendered as editorfont for the given font size.
2016 * This function is inefficient; only call when we need the exact width.
2017 */
2018-
2019-uint32_t text_width(const std::string& text, int ptsize);
2020+int text_width(const std::string& text, int ptsize = UI_FONT_SIZE_SMALL);
2021
2022 /**
2023- * Returns the exact height of the text rendered as editorfont for the given font size.
2024+ * Returns the exact height of the text rendered for the given font size and face.
2025 * This function is inefficient; only call when we need the exact height.
2026 */
2027-uint32_t text_height(const std::string& text, int ptsize);
2028+int text_height(int ptsize = UI_FONT_SIZE_SMALL, UI::FontSet::Face face = UI::FontSet::Face::kSans);
2029
2030 /**
2031 * Checks it the given string is RichText or not. Does not do validity checking.
2032@@ -95,14 +95,14 @@
2033 * smaller until it fits 'width'. The resulting font size will not go below
2034 * 'kMinimumFontSize'.
2035 */
2036-const Image* autofit_ui_text(const std::string& text,
2037- int width = 0,
2038- RGBColor color = UI_FONT_CLR_FG,
2039- int fontsize = UI_FONT_SIZE_SMALL);
2040+std::shared_ptr<const UI::RenderedText> autofit_ui_text(const std::string& text,
2041+ int width = 0,
2042+ RGBColor color = UI_FONT_CLR_FG,
2043+ int fontsize = UI_FONT_SIZE_SMALL);
2044
2045 namespace UI {
2046
2047-Align mirror_alignment(Align alignment);
2048+Align mirror_alignment(Align alignment, const std::string& checkme = "");
2049
2050 void center_vertically(uint32_t h, Vector2i* pt);
2051 void correct_for_align(Align, uint32_t w, Vector2i* pt);
2052
2053=== modified file 'src/graphic/texture.cc'
2054--- src/graphic/texture.cc 2016-10-24 14:07:28 +0000
2055+++ src/graphic/texture.cc 2017-05-23 21:33:09 +0000
2056@@ -172,7 +172,7 @@
2057 void Texture::init(uint16_t w, uint16_t h) {
2058 blit_data_ = {
2059 0, // initialized below
2060- w, h, Rectf(0, 0, w, h),
2061+ w, h, Rectf(0.f, 0.f, w, h),
2062 };
2063 if (w * h == 0) {
2064 return;
2065
2066=== modified file 'src/graphic/texture_atlas.cc'
2067--- src/graphic/texture_atlas.cc 2017-01-25 18:55:59 +0000
2068+++ src/graphic/texture_atlas.cc 2017-05-23 21:33:09 +0000
2069@@ -134,13 +134,13 @@
2070 }
2071
2072 std::unique_ptr<Texture> texture_atlas(new Texture(root->r.w, root->r.h));
2073- texture_atlas->fill_rect(Rectf(0, 0, root->r.w, root->r.h), RGBAColor(0, 0, 0, 0));
2074+ texture_atlas->fill_rect(Rectf(0.f, 0.f, root->r.w, root->r.h), RGBAColor(0, 0, 0, 0));
2075
2076 const auto packed_texture_id = texture_atlas->blit_data().texture_id;
2077 for (Block& block : packed) {
2078 texture_atlas->blit(
2079 Rectf(block.node->r.x, block.node->r.y, block.texture->width(), block.texture->height()),
2080- *block.texture, Rectf(0, 0, block.texture->width(), block.texture->height()), 1.,
2081+ *block.texture, Rectf(0.f, 0.f, block.texture->width(), block.texture->height()), 1.,
2082 BlendMode::Copy);
2083
2084 pack_info->emplace_back(PackedTexture(
2085
2086=== removed file 'src/graphic/texture_cache.cc'
2087--- src/graphic/texture_cache.cc 2017-01-25 18:55:59 +0000
2088+++ src/graphic/texture_cache.cc 1970-01-01 00:00:00 +0000
2089@@ -1,87 +0,0 @@
2090-/*
2091- * Copyright (C) 2006-2017 by the Widelands Development Team
2092- *
2093- * This program is free software; you can redistribute it and/or
2094- * modify it under the terms of the GNU General Public License
2095- * as published by the Free Software Foundation; either version 2
2096- * of the License, or (at your option) any later version.
2097- *
2098- * This program is distributed in the hope that it will be useful,
2099- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2100- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2101- * GNU General Public License for more details.
2102- *
2103- * You should have received a copy of the GNU General Public License
2104- * along with this program; if not, write to the Free Software
2105- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
2106- *
2107- */
2108-
2109-#include "graphic/texture_cache.h"
2110-
2111-#include <memory>
2112-
2113-#include <SDL.h>
2114-#include <stdint.h>
2115-
2116-#include "graphic/texture.h"
2117-
2118-// The implementation took inspiration from
2119-// http://timday.bitbucket.org/lru.html, but our use case here is a little
2120-// different.
2121-
2122-TextureCache::TextureCache(uint32_t max_size_in_bytes)
2123- : max_size_in_bytes_(max_size_in_bytes), size_in_bytes_(0) {
2124-}
2125-
2126-TextureCache::~TextureCache() {
2127- flush();
2128-}
2129-
2130-void TextureCache::flush() {
2131- entries_.clear();
2132- access_history_.clear();
2133- size_in_bytes_ = 0;
2134-}
2135-
2136-Texture* TextureCache::get(const std::string& hash) {
2137- const auto it = entries_.find(hash);
2138- if (it == entries_.end())
2139- return nullptr;
2140-
2141- // Move this to the back of the access list to signal that we have used this
2142- // recently and update last access time.
2143- access_history_.splice(access_history_.end(), access_history_, it->second.list_iterator);
2144- it->second.last_access = SDL_GetTicks();
2145- return it->second.texture.get();
2146-}
2147-
2148-Texture* TextureCache::insert(const std::string& hash, std::unique_ptr<Texture> texture) {
2149- assert(entries_.find(hash) == entries_.end());
2150-
2151- const uint32_t texture_size = texture->width() * texture->height() * 4;
2152- while (size_in_bytes_ + texture_size > max_size_in_bytes_) {
2153- drop();
2154- }
2155-
2156- // Record hash as most-recently-used.
2157- AccessHistory::iterator it = access_history_.insert(access_history_.end(), hash);
2158- size_in_bytes_ += texture_size;
2159- return entries_.insert(make_pair(hash, Entry{std::move(texture), SDL_GetTicks(), it}))
2160- .first->second.texture.get();
2161-}
2162-
2163-void TextureCache::drop() {
2164- assert(!access_history_.empty());
2165-
2166- // Identify least recently used key
2167- const auto it = entries_.find(access_history_.front());
2168- assert(it != entries_.end());
2169-
2170- const uint32_t texture_size = it->second.texture->width() * it->second.texture->height() * 4;
2171- size_in_bytes_ -= texture_size;
2172-
2173- // Erase both elements to completely purge record
2174- entries_.erase(it);
2175- access_history_.pop_front();
2176-}
2177
2178=== modified file 'src/graphic/wordwrap.cc'
2179--- src/graphic/wordwrap.cc 2017-05-13 13:14:29 +0000
2180+++ src/graphic/wordwrap.cc 2017-05-23 21:33:09 +0000
2181@@ -119,7 +119,7 @@
2182 }
2183
2184 // Optimism: perhaps the entire line fits?
2185- if (text_width(text.substr(line_start, orig_end - line_start), fontsize_) <=
2186+ if (uint32_t(text_width(text.substr(line_start, orig_end - line_start), fontsize_)) <=
2187 wrapwidth_ - safety_margin) {
2188 line_end = orig_end;
2189 next_line_start = orig_end + 1;
2190@@ -192,7 +192,8 @@
2191 // Now make sure that it really fits.
2192 std::string::size_type test_cutoff = line_start + end * 2 / 3;
2193 while ((end > 0) && (static_cast<uint32_t>(line_start + end) > test_cutoff)) {
2194- if (text_width(text.substr(line_start, end), fontsize_) > wrapwidth_ - safety_margin) {
2195+ if (uint32_t(text_width(text.substr(line_start, end), fontsize_)) >
2196+ wrapwidth_ - safety_margin) {
2197 --end;
2198 } else {
2199 break;
2200@@ -223,7 +224,7 @@
2201 // calc_width_for_wrapping is fast, but it will underestimate the width.
2202 // So, we test again with text_width to make sure that the line really fits.
2203 return quick_width(i18n::make_ligatures(text.c_str())) <= wrapwidth_ - safety_margin &&
2204- text_width(text, fontsize_) <= wrapwidth_ - safety_margin;
2205+ uint32_t(text_width(text, fontsize_)) <= wrapwidth_ - safety_margin;
2206 }
2207
2208 /**
2209@@ -247,12 +248,7 @@
2210 * Compute the total height of the word-wrapped text.
2211 */
2212 uint32_t WordWrap::height() const {
2213- uint16_t fontheight = 0;
2214- if (!lines_.empty()) {
2215- fontheight = text_height(lines_[0].text, fontsize_);
2216- }
2217-
2218- return fontheight * (lines_.size()) + 2 * kLineMargin;
2219+ return text_height(fontsize_) * (lines_.size()) + 2 * kLineMargin;
2220 }
2221
2222 /**
2223@@ -305,9 +301,9 @@
2224
2225 Align alignment = mirror_alignment(align);
2226
2227- uint16_t fontheight = text_height(lines_[0].text, fontsize_);
2228+ const int fontheight = text_height(fontsize_);
2229 for (uint32_t line = 0; line < lines_.size(); ++line, where.y += fontheight) {
2230- if (where.y >= dst.height() || int32_t(where.y + fontheight) <= 0)
2231+ if (where.y >= dst.height() || (where.y + fontheight) <= 0)
2232 continue;
2233
2234 Vector2i point(where.x, where.y);
2235@@ -316,10 +312,10 @@
2236 point.x += wrapwidth_ - kLineMargin;
2237 }
2238
2239- const Image* entry_text_im = UI::g_fh1->render(
2240+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(
2241 as_editorfont(lines_[line].text, fontsize_ - UI::g_fh1->fontset()->size_offset(), color_));
2242- UI::correct_for_align(alignment, entry_text_im->width(), &point);
2243- dst.blit(point, entry_text_im);
2244+ UI::correct_for_align(alignment, rendered_text->width(), &point);
2245+ rendered_text->draw(dst, point);
2246
2247 if (draw_caret_ && line == caretline) {
2248 std::string line_to_caret = lines_[line].text.substr(0, caretpos);
2249
2250=== modified file 'src/graphic/wordwrap.h'
2251--- src/graphic/wordwrap.h 2017-04-29 14:57:30 +0000
2252+++ src/graphic/wordwrap.h 2017-05-23 21:33:09 +0000
2253@@ -27,8 +27,8 @@
2254 #include "base/vector.h"
2255 #include "graphic/align.h"
2256 #include "graphic/color.h"
2257+#include "graphic/text/sdl_ttf_font.h"
2258 #include "graphic/text_constants.h"
2259-#include "graphic/text/sdl_ttf_font.h"
2260
2261 class RenderTarget;
2262
2263
2264=== modified file 'src/logic/editor_game_base.h'
2265--- src/logic/editor_game_base.h 2017-02-14 19:59:29 +0000
2266+++ src/logic/editor_game_base.h 2017-05-23 21:33:09 +0000
2267@@ -36,7 +36,7 @@
2268 namespace UI {
2269 struct ProgressWindow;
2270 }
2271-struct FullscreenMenuLaunchGame;
2272+class FullscreenMenuLaunchGame;
2273 class InteractiveBase;
2274 class InteractiveGameBase; // TODO(GunChleoc): Get rid
2275
2276@@ -78,7 +78,7 @@
2277 class EditorGameBase {
2278 public:
2279 friend class InteractiveBase;
2280- friend struct FullscreenMenuLaunchGame;
2281+ friend class FullscreenMenuLaunchGame;
2282 friend struct GameClassPacket;
2283
2284 EditorGameBase(LuaInterface* lua);
2285
2286=== modified file 'src/logic/map_objects/map_object.cc'
2287--- src/logic/map_objects/map_object.cc 2017-05-13 18:48:26 +0000
2288+++ src/logic/map_objects/map_object.cc 2017-05-23 21:33:09 +0000
2289@@ -20,6 +20,7 @@
2290 #include "logic/map_objects/map_object.h"
2291
2292 #include <algorithm>
2293+#include <cmath>
2294 #include <cstdarg>
2295 #include <cstdio>
2296 #include <cstring>
2297@@ -463,30 +464,26 @@
2298 }
2299
2300 // Rendering text is expensive, so let's just do it for only a few sizes.
2301- scale = std::round(scale);
2302- if (scale == 0.f) {
2303+ // The forumla is a bit fancy to avoid too much text overlap.
2304+ scale = std::round(2.f * (scale > 1.f ? std::sqrt(scale) : std::pow(scale, 2.f))) / 2.f;
2305+ if (scale < 1.f) {
2306 return;
2307 }
2308 const int font_size = scale * UI_FONT_SIZE_SMALL;
2309
2310 // We always render this so we can have a stable position for the statistics string.
2311- const Image* rendered_census_info =
2312- UI::g_fh1->render(as_condensed(census, UI::Align::kCenter, font_size), 120);
2313-
2314- const Vector2i base_pos = field_on_dst.cast<int>() - Vector2i(0, 48) * scale;
2315- Vector2i census_pos(base_pos);
2316- UI::correct_for_align(UI::Align::kCenter, rendered_census_info->width(), &census_pos);
2317+ std::shared_ptr<const UI::RenderedText> rendered_census =
2318+ UI::g_fh1->render(as_condensed(census, UI::Align::kCenter, font_size), 120 * scale);
2319+ Vector2i position = field_on_dst.cast<int>() - Vector2i(0, 48) * scale;
2320 if (draw_text & TextToDraw::kCensus) {
2321- dst->blit(census_pos, rendered_census_info, BlendMode::UseAlpha);
2322+ rendered_census->draw(*dst, position, UI::Align::kCenter);
2323 }
2324
2325 if (draw_text & TextToDraw::kStatistics && !statictics.empty()) {
2326- Vector2i statistics_pos =
2327- base_pos + Vector2i(0, rendered_census_info->height() / 2 + 10 * scale);
2328- const Image* rendered_statictics =
2329+ std::shared_ptr<const UI::RenderedText> rendered_statistics =
2330 UI::g_fh1->render(as_condensed(statictics, UI::Align::kCenter, font_size));
2331- UI::correct_for_align(UI::Align::kCenter, rendered_statictics->width(), &statistics_pos);
2332- dst->blit(statistics_pos, rendered_statictics, BlendMode::UseAlpha);
2333+ position.y += rendered_census->height() + text_height(font_size) / 4;
2334+ rendered_statistics->draw(*dst, position, UI::Align::kCenter);
2335 }
2336 }
2337
2338
2339=== modified file 'src/network/network.h'
2340--- src/network/network.h 2017-05-11 16:13:34 +0000
2341+++ src/network/network.h 2017-05-23 21:33:09 +0000
2342@@ -119,7 +119,7 @@
2343 bool end_of_file() const override;
2344
2345 private:
2346- friend struct Deserializer;
2347+ friend class Deserializer;
2348 std::vector<uint8_t> buffer;
2349 size_t index_;
2350 };
2351
2352=== modified file 'src/ui_basic/button.cc'
2353--- src/ui_basic/button.cc 2017-05-14 14:40:24 +0000
2354+++ src/ui_basic/button.cc 2017-05-23 21:33:09 +0000
2355@@ -58,9 +58,7 @@
2356 clr_down_(229, 161, 2) {
2357 // Automatically resize for font height and give it a margin.
2358 if (h < 1) {
2359- int new_height =
2360- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
2361- 4;
2362+ int new_height = text_height() + 4;
2363 set_desired_size(w, new_height);
2364 set_size(w, new_height);
2365 }
2366@@ -207,13 +205,12 @@
2367
2368 } else if (title_.length()) {
2369 // Otherwise draw title string centered
2370- const Image* entry_text_im =
2371+ std::shared_ptr<const UI::RenderedText> rendered_text =
2372 autofit_ui_text(title_, get_inner_w() - 2 * kButtonImageMargin,
2373 is_monochrome ? UI_FONT_CLR_DISABLED : UI_FONT_CLR_FG);
2374 // Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
2375- dst.blit(
2376- Vector2i((get_w() - entry_text_im->width()) / 2, (get_h() - entry_text_im->height()) / 2),
2377- entry_text_im);
2378+ rendered_text->draw(dst, Vector2i((get_w() - rendered_text->width()) / 2,
2379+ (get_h() - rendered_text->height()) / 2));
2380 }
2381
2382 // draw border
2383
2384=== modified file 'src/ui_basic/checkbox.cc'
2385--- src/ui_basic/checkbox.cc 2017-04-22 12:19:21 +0000
2386+++ src/ui_basic/checkbox.cc 2017-05-23 21:33:09 +0000
2387@@ -45,8 +45,8 @@
2388 : Panel(parent, p.x, p.y, kStateboxSize, kStateboxSize, tooltip_text),
2389 flags_(Is_Enabled),
2390 pic_graphics_(pic),
2391- label_text_(""),
2392- rendered_text_(nullptr) {
2393+ rendered_text_(nullptr),
2394+ label_text_("") {
2395 uint16_t w = pic->width();
2396 uint16_t h = pic->height();
2397 set_desired_size(w, h);
2398@@ -63,8 +63,8 @@
2399 : Panel(parent, p.x, p.y, std::max(width, kStateboxSize), kStateboxSize, tooltip_text),
2400 flags_(Is_Enabled),
2401 pic_graphics_(g_gr->images().get("images/ui_basic/checkbox_light.png")),
2402- label_text_(label_text),
2403- rendered_text_(nullptr) {
2404+ rendered_text_(nullptr),
2405+ label_text_(label_text) {
2406 set_flags(Has_Text, !label_text_.empty());
2407 layout();
2408 }
2409@@ -83,7 +83,7 @@
2410 rendered_text_ = label_text_.empty() ?
2411 nullptr :
2412 UI::g_fh1->render(as_uifont(label_text_), text_width(get_w(), pic_width));
2413- if (rendered_text_) {
2414+ if (rendered_text_.get()) {
2415 w = std::max(rendered_text_->width() + kPadding + pic_width, w);
2416 h = std::max(rendered_text_->height(), h);
2417 }
2418@@ -144,16 +144,16 @@
2419 } else {
2420 static_assert(0 <= kStateboxSize, "assert(0 <= STATEBOX_WIDTH) failed.");
2421 static_assert(0 <= kStateboxSize, "assert(0 <= STATEBOX_HEIGHT) failed.");
2422- Vector2i image_anchor(0, 0);
2423+ Vector2i image_anchor = Vector2i::zero();
2424 Vector2i text_anchor(kStateboxSize + kPadding, 0);
2425
2426- if (rendered_text_) {
2427+ if (rendered_text_.get()) {
2428 if (UI::g_fh1->fontset()->is_rtl()) {
2429 text_anchor.x = 0;
2430 image_anchor.x = rendered_text_->width() + kPadding;
2431 image_anchor.y = (get_h() - kStateboxSize) / 2;
2432 }
2433- dst.blit(text_anchor, rendered_text_, BlendMode::UseAlpha);
2434+ rendered_text_->draw(dst, text_anchor);
2435 }
2436
2437 dst.blitrect(
2438
2439=== modified file 'src/ui_basic/checkbox.h'
2440--- src/ui_basic/checkbox.h 2017-02-12 09:10:57 +0000
2441+++ src/ui_basic/checkbox.h 2017-05-23 21:33:09 +0000
2442@@ -90,8 +90,8 @@
2443 flags_ |= flags;
2444 }
2445 const Image* pic_graphics_;
2446+ std::shared_ptr<const UI::RenderedText> rendered_text_;
2447 const std::string label_text_;
2448- const Image* rendered_text_;
2449 };
2450
2451 /**
2452
2453=== modified file 'src/ui_basic/editbox.cc'
2454--- src/ui_basic/editbox.cc 2017-05-13 11:25:24 +0000
2455+++ src/ui_basic/editbox.cc 2017-05-23 21:33:09 +0000
2456@@ -80,15 +80,7 @@
2457 int margin_y,
2458 const Image* background,
2459 int font_size)
2460- : Panel(parent,
2461- x,
2462- y,
2463- w,
2464- h > 0 ? h :
2465- UI::g_fh1->render(as_editorfont(UI::g_fh1->fontset()->representative_character(),
2466- font_size))
2467- ->height() +
2468- 2 * margin_y),
2469+ : Panel(parent, x, y, w, h > 0 ? h : text_height(font_size) + 2 * margin_y),
2470 m_(new EditBoxImpl),
2471 history_active_(false),
2472 history_position_(-1) {
2473@@ -380,15 +372,10 @@
2474
2475 const int max_width = get_w() - 2 * kMarginX;
2476
2477- const Image* entry_text_im = UI::g_fh1->render(as_editorfont(m_->text, m_->fontsize));
2478+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_editorfont(m_->text, m_->fontsize));
2479
2480- const int linewidth = entry_text_im->width();
2481- const int lineheight =
2482- m_->text.empty() ?
2483- UI::g_fh1->render(
2484- as_editorfont(UI::g_fh1->fontset()->representative_character(), m_->fontsize))
2485- ->height() :
2486- entry_text_im->height();
2487+ const int linewidth = rendered_text->width();
2488+ const int lineheight = m_->text.empty() ? text_height(m_->fontsize) : rendered_text->height();
2489
2490 Vector2i point(kMarginX, get_h() / 2);
2491 if (m_->align == UI::Align::kRight) {
2492@@ -405,18 +392,18 @@
2493 // We want this always on, e.g. for mixed language savegame filenames
2494 if (i18n::has_rtl_character(m_->text.c_str(), 100)) { // Restrict check for efficiency
2495 // TODO(GunChleoc): Arabic: Fix scrolloffset
2496- dst.blitrect(point, entry_text_im, Recti(linewidth - max_width, 0, linewidth, lineheight));
2497+ rendered_text->draw(dst, point, Recti(linewidth - max_width, 0, linewidth, lineheight));
2498 } else {
2499 if (m_->align == UI::Align::kRight) {
2500 // TODO(GunChleoc): Arabic: Fix scrolloffset
2501- dst.blitrect(point, entry_text_im,
2502- Recti(point.x + m_->scrolloffset + kMarginX, 0, max_width, lineheight));
2503+ rendered_text->draw(
2504+ dst, point, Recti(point.x + m_->scrolloffset + kMarginX, 0, max_width, lineheight));
2505 } else {
2506- dst.blitrect(point, entry_text_im, Recti(-m_->scrolloffset, 0, max_width, lineheight));
2507+ rendered_text->draw(dst, point, Recti(-m_->scrolloffset, 0, max_width, lineheight));
2508 }
2509 }
2510 } else {
2511- dst.blitrect(point, entry_text_im, Recti(0, 0, max_width, lineheight));
2512+ rendered_text->draw(dst, point, Recti(0, 0, max_width, lineheight));
2513 }
2514
2515 if (has_focus()) {
2516@@ -425,7 +412,7 @@
2517 // TODO(GunChleoc): Arabic: Fix cursor position for BIDI text.
2518 int caret_x = text_width(line_to_caret, m_->fontsize);
2519
2520- const uint16_t fontheight = text_height(m_->text, m_->fontsize);
2521+ const uint16_t fontheight = text_height(m_->fontsize);
2522
2523 const Image* caret_image = g_gr->images().get("images/ui_basic/caret.png");
2524 Vector2i caretpt = Vector2i::zero();
2525
2526=== modified file 'src/ui_basic/listselect.cc'
2527--- src/ui_basic/listselect.cc 2017-05-14 14:40:24 +0000
2528+++ src/ui_basic/listselect.cc 2017-05-23 21:33:09 +0000
2529@@ -53,9 +53,7 @@
2530 const Image* button_background,
2531 const ListselectLayout selection_mode)
2532 : Panel(parent, x, y, w, h),
2533- lineheight_(
2534- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
2535- kMargin),
2536+ lineheight_(text_height() + kMargin),
2537 scrollbar_(this, get_w() - Scrollbar::kSize, 0, Scrollbar::kSize, h, button_background),
2538 scrollpos_(0),
2539 selection_(no_selection_index()),
2540@@ -333,10 +331,10 @@
2541 if (selection_mode_ == ListselectLayout::kDropdown) {
2542 for (size_t i = 0; i < entry_records_.size(); ++i) {
2543 const EntryRecord& er = *entry_records_[i];
2544- const Image* entry_text_im = UI::g_fh1->render(as_uifont(
2545+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_uifont(
2546 richtext_escape(er.name), UI_FONT_SIZE_SMALL, er.use_clr ? er.clr : UI_FONT_CLR_FG));
2547 int picw = max_pic_width_ ? max_pic_width_ + 10 : 0;
2548- int difference = entry_text_im->width() + picw + 8 - get_eff_w();
2549+ int difference = rendered_text->width() + picw + 8 - get_eff_w();
2550 if (difference > 0) {
2551 set_size(get_w() + difference, get_h());
2552 }
2553@@ -378,10 +376,10 @@
2554 assert(eff_h < std::numeric_limits<int32_t>::max());
2555
2556 const EntryRecord& er = *entry_records_[idx];
2557- const Image* entry_text_im = UI::g_fh1->render(as_uifont(
2558+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_uifont(
2559 richtext_escape(er.name), UI_FONT_SIZE_SMALL, er.use_clr ? er.clr : UI_FONT_CLR_FG));
2560
2561- int lineheight = std::max(get_lineheight(), entry_text_im->height());
2562+ int lineheight = std::max(get_lineheight(), rendered_text->height());
2563
2564 // Don't draw over the bottom edge
2565 lineheight = std::min(eff_h - y, lineheight);
2566@@ -421,23 +419,22 @@
2567 er.pic);
2568 }
2569
2570+ // Position the text according to alignment
2571 Align alignment = i18n::has_rtl_character(er.name.c_str(), 20) ? Align::kRight : Align::kLeft;
2572 if (alignment == UI::Align::kRight) {
2573 point.x += maxw - picw;
2574 }
2575
2576- UI::correct_for_align(alignment, entry_text_im->width(), &point);
2577-
2578 // Shift for image width
2579 if (!UI::g_fh1->fontset()->is_rtl()) {
2580 point.x += picw;
2581 }
2582
2583 // Fix vertical position for mixed font heights
2584- if (get_lineheight() > entry_text_im->height()) {
2585- point.y += (lineheight_ - entry_text_im->height()) / 2;
2586+ if (get_lineheight() > rendered_text->height()) {
2587+ point.y += (lineheight_ - rendered_text->height()) / 2;
2588 } else {
2589- point.y -= (entry_text_im->height() - lineheight_) / 2;
2590+ point.y -= (rendered_text->height() - lineheight_) / 2;
2591 }
2592
2593 // Don't draw over the bottom edge
2594@@ -445,21 +442,7 @@
2595 if (lineheight < 0) {
2596 break;
2597 }
2598-
2599- // Crop to column width while blitting
2600- if ((alignment == UI::Align::kRight) &&
2601- (maxw + picw) < static_cast<uint32_t>(entry_text_im->width())) {
2602- // Fix positioning for BiDi languages.
2603- point.x = 0;
2604-
2605- // We want this always on, e.g. for mixed language savegame filenames, or the languages
2606- // list
2607- dst.blitrect(point, entry_text_im, Recti(entry_text_im->width() - maxw + picw, 0, maxw,
2608- entry_text_im->height()));
2609- } else {
2610- dst.blitrect(point, entry_text_im, Recti(0, 0, maxw, lineheight));
2611- }
2612-
2613+ rendered_text->draw(dst, point, Recti(0, 0, maxw, lineheight), alignment, RenderedText::CropMode::kSelf);
2614 y += get_lineheight();
2615 ++idx;
2616 }
2617
2618=== modified file 'src/ui_basic/messagebox.cc'
2619--- src/ui_basic/messagebox.cc 2017-02-23 17:58:25 +0000
2620+++ src/ui_basic/messagebox.cc 2017-05-23 21:33:09 +0000
2621@@ -48,14 +48,14 @@
2622 const int margin = 5;
2623 int width, height = 0;
2624 {
2625- const Image* temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth);
2626+ std::shared_ptr<const UI::RenderedText> temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth);
2627 width = temp_rendered_text->width();
2628 height = temp_rendered_text->height();
2629 }
2630
2631 // Stupid heuristic to avoid excessively long lines
2632 if (height < 2 * UI_FONT_SIZE_SMALL) {
2633- const Image* temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth / 2);
2634+ std::shared_ptr<const UI::RenderedText> temp_rendered_text = g_fh1->render(as_uifont(text), maxwidth / 2);
2635 width = temp_rendered_text->width();
2636 height = temp_rendered_text->height();
2637 }
2638
2639=== modified file 'src/ui_basic/multilineeditbox.cc'
2640--- src/ui_basic/multilineeditbox.cc 2017-05-13 11:25:24 +0000
2641+++ src/ui_basic/multilineeditbox.cc 2017-05-23 21:33:09 +0000
2642@@ -91,7 +91,7 @@
2643 const Image* button_background)
2644 : Panel(parent, x, y, w, h), d_(new Data(*this, button_background)) {
2645 d_->background = background;
2646- d_->lineheight = text_height(g_fh1->fontset()->representative_character(), UI_FONT_SIZE_SMALL);
2647+ d_->lineheight = text_height();
2648 set_handle_mouse(true);
2649 set_can_focus(true);
2650 set_thinks(false);
2651
2652=== modified file 'src/ui_basic/multilinetextarea.cc'
2653--- src/ui_basic/multilinetextarea.cc 2017-05-13 11:25:24 +0000
2654+++ src/ui_basic/multilinetextarea.cc 2017-05-23 21:33:09 +0000
2655@@ -55,10 +55,7 @@
2656
2657 scrollbar_.moved.connect(boost::bind(&MultilineTextarea::scrollpos_changed, this, _1));
2658
2659- scrollbar_.set_singlestepsize(
2660- UI::g_fh1->render(
2661- as_uifont(UI::g_fh1->fontset()->representative_character(), UI_FONT_SIZE_SMALL))
2662- ->height());
2663+ scrollbar_.set_singlestepsize(text_height());
2664 scrollbar_.set_steps(1);
2665 scrollbar_.set_force_draw(scrollmode_ == ScrollMode::kScrollNormalForced ||
2666 scrollmode_ == ScrollMode::kScrollLogForced);
2667@@ -88,13 +85,10 @@
2668 for (int i = 0; i < 2; ++i) {
2669 if (!is_richtext(text_)) {
2670 use_old_renderer_ = false;
2671- const Image* text_im =
2672- UI::g_fh1->render(make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN);
2673- height = text_im->height();
2674+ height = UI::g_fh1->render(make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN)->height();
2675 } else if (force_new_renderer_) {
2676 use_old_renderer_ = false;
2677- const Image* text_im = UI::g_fh1->render(text_, get_eff_w() - 2 * RICHTEXT_MARGIN);
2678- height = text_im->height();
2679+ height = UI::g_fh1->render(text_, get_eff_w() - 2 * RICHTEXT_MARGIN)->height();
2680 } else {
2681 use_old_renderer_ = true;
2682 rt.set_width(get_eff_w() - 2 * RICHTEXT_MARGIN);
2683@@ -150,19 +144,14 @@
2684 if (use_old_renderer_) {
2685 rt.draw(dst, Vector2i(RICHTEXT_MARGIN, RICHTEXT_MARGIN - scrollbar_.get_scrollpos()));
2686 } else {
2687- const Image* text_im;
2688- if (!is_richtext(text_)) {
2689- text_im = UI::g_fh1->render(make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN);
2690- } else {
2691- text_im = UI::g_fh1->render(text_, get_eff_w() - 2 * RICHTEXT_MARGIN);
2692- }
2693-
2694- uint32_t blit_width = std::min(text_im->width(), static_cast<int>(get_eff_w()));
2695- uint32_t blit_height = std::min(text_im->height(), static_cast<int>(get_inner_h()));
2696+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(
2697+ is_richtext(text_) ? text_ : make_richtext(), get_eff_w() - 2 * RICHTEXT_MARGIN);
2698+ uint32_t blit_width = std::min(rendered_text->width(), static_cast<int>(get_eff_w()));
2699+ uint32_t blit_height = std::min(rendered_text->height(), get_inner_h());
2700
2701 if (blit_width > 0 && blit_height > 0) {
2702 int anchor = 0;
2703- Align alignment = mirror_alignment(align_);
2704+ Align alignment = mirror_alignment(align_, text_);
2705 switch (alignment) {
2706 case UI::Align::kCenter:
2707 anchor = (get_eff_w() - blit_width) / 2;
2708@@ -173,10 +162,8 @@
2709 case UI::Align::kLeft:
2710 anchor = RICHTEXT_MARGIN;
2711 }
2712-
2713- dst.blitrect(Vector2i(anchor, 0), text_im,
2714- Recti(0, scrollbar_.get_scrollpos(), blit_width, blit_height),
2715- BlendMode::UseAlpha);
2716+ rendered_text->draw(dst, Vector2i(anchor, 0),
2717+ Recti(0, scrollbar_.get_scrollpos(), blit_width, blit_height));
2718 }
2719 }
2720 }
2721
2722=== modified file 'src/ui_basic/panel.cc'
2723--- src/ui_basic/panel.cc 2017-05-13 11:25:24 +0000
2724+++ src/ui_basic/panel.cc 2017-05-23 21:33:09 +0000
2725@@ -1060,13 +1060,14 @@
2726 text_to_render = as_tooltip(text);
2727 }
2728
2729- static const uint32_t TIP_WIDTH_MAX = 360;
2730- const Image* rendered_text = g_fh1->render(text_to_render, TIP_WIDTH_MAX);
2731- if (!rendered_text) {
2732+ constexpr uint32_t kTipWidthMax = 360;
2733+ std::shared_ptr<const UI::RenderedText> rendered_text = g_fh1->render(text_to_render, kTipWidthMax);
2734+ if (rendered_text->rects.empty()) {
2735 return false;
2736 }
2737- uint16_t tip_width = rendered_text->width() + 4;
2738- uint16_t tip_height = rendered_text->height() + 4;
2739+
2740+ const uint16_t tip_width = rendered_text->width() + 4;
2741+ const uint16_t tip_height = rendered_text->height() + 4;
2742
2743 Recti r(WLApplication::get()->get_mouse_position() + Vector2i(2, 32), tip_width, tip_height);
2744 const Vector2i tooltip_bottom_right = r.opposite_of_origin();
2745@@ -1078,7 +1079,7 @@
2746
2747 dst.fill_rect(r, RGBColor(63, 52, 34));
2748 dst.draw_rect(r, RGBColor(0, 0, 0));
2749- dst.blit(r.origin() + Vector2i(2, 2), rendered_text);
2750+ rendered_text->draw(dst, r.origin() + Vector2i(2, 2));
2751 return true;
2752 }
2753 }
2754
2755=== modified file 'src/ui_basic/panel.h'
2756--- src/ui_basic/panel.h 2017-02-27 13:53:04 +0000
2757+++ src/ui_basic/panel.h 2017-05-23 21:33:09 +0000
2758@@ -28,7 +28,10 @@
2759 #include <boost/signals2/trackable.hpp>
2760
2761 #include "base/macros.h"
2762+#include "base/rect.h"
2763 #include "base/vector.h"
2764+#include "graphic/align.h"
2765+#include "graphic/font_handler1.h"
2766
2767 class RenderTarget;
2768 class Image;
2769
2770=== modified file 'src/ui_basic/progressbar.cc'
2771--- src/ui_basic/progressbar.cc 2017-04-22 12:19:21 +0000
2772+++ src/ui_basic/progressbar.cc 2017-05-23 21:33:09 +0000
2773@@ -88,10 +88,9 @@
2774 const std::string progress_text = (boost::format("<font color=%s>%u%%</font>") %
2775 UI_FONT_CLR_BRIGHT.hex_value() % floor(fraction * 100.f))
2776 .str();
2777- const Image* rendered_text = UI::g_fh1->render(as_uifont(progress_text));
2778+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_uifont(progress_text));
2779 Vector2i pos(get_w() / 2, get_h() / 2);
2780- UI::correct_for_align(UI::Align::kCenter, rendered_text->width(), &pos);
2781 UI::center_vertically(rendered_text->height(), &pos);
2782- dst.blit(pos, rendered_text, BlendMode::UseAlpha);
2783+ rendered_text->draw(dst, pos, UI::Align::kCenter);
2784 }
2785 }
2786
2787=== modified file 'src/ui_basic/progresswindow.cc'
2788--- src/ui_basic/progresswindow.cc 2017-05-13 18:48:26 +0000
2789+++ src/ui_basic/progresswindow.cc 2017-05-23 21:33:09 +0000
2790@@ -63,8 +63,7 @@
2791 label_center_.x = get_w() / 2;
2792 label_center_.y = get_h() * PROGRESS_LABEL_POSITION_Y / 100;
2793
2794- const uint32_t h =
2795- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height();
2796+ const uint32_t h = text_height();
2797
2798 label_rectangle_.x = get_w() / 4;
2799 label_rectangle_.w = get_w() / 2;
2800@@ -99,11 +98,10 @@
2801 draw(rt);
2802
2803 rt.fill_rect(label_rectangle_, PROGRESS_FONT_COLOR_BG);
2804- const Image* rendered_text =
2805+ std::shared_ptr<const UI::RenderedText> rendered_text =
2806 UI::g_fh1->render(as_uifont(description, UI_FONT_SIZE_SMALL, PROGRESS_FONT_COLOR_FG));
2807- UI::correct_for_align(UI::Align::kCenter, rendered_text->width(), &label_center_);
2808 UI::center_vertically(rendered_text->height(), &label_center_);
2809- rt.blit(label_center_, rendered_text, BlendMode::UseAlpha);
2810+ rendered_text->draw(rt, label_center_, UI::Align::kCenter);
2811
2812 #ifdef _WIN32
2813 // Pump events to prevent "not responding" on windows
2814
2815=== modified file 'src/ui_basic/slider.cc'
2816--- src/ui_basic/slider.cc 2017-04-22 12:19:21 +0000
2817+++ src/ui_basic/slider.cc 2017-05-23 21:33:09 +0000
2818@@ -508,12 +508,7 @@
2819 w / (2 * labels_in.size()) - cursor_size / 2,
2820 0,
2821 w - (w / labels_in.size()) + cursor_size,
2822- h -
2823- UI::g_fh1->render(as_condensed(UI::g_fh1->fontset()->representative_character(),
2824- UI::Align::kLeft,
2825- UI_FONT_SIZE_SMALL - 2))
2826- ->height() -
2827- 2,
2828+ h - text_height(UI_FONT_SIZE_SMALL - 2, UI::FontSet::Face::kCondensed) - 2,
2829 0,
2830 labels_in.size() - 1,
2831 value_,
2832@@ -538,11 +533,10 @@
2833 uint32_t gap_n = get_w() / labels.size();
2834
2835 for (uint32_t i = 0; i < labels.size(); i++) {
2836- const Image* rendered_text =
2837+ std::shared_ptr<const UI::RenderedText> rendered_text =
2838 UI::g_fh1->render(as_condensed(labels[i], UI::Align::kCenter, UI_FONT_SIZE_SMALL - 2));
2839- Vector2i point(gap_1 + i * gap_n, get_h() - rendered_text->height());
2840- UI::correct_for_align(UI::Align::kCenter, rendered_text->width(), &point);
2841- dst.blit(point, rendered_text, BlendMode::UseAlpha);
2842+ rendered_text->draw(
2843+ dst, Vector2i(gap_1 + i * gap_n, get_h() - rendered_text->height()), UI::Align::kCenter);
2844 }
2845 }
2846
2847@@ -557,13 +551,8 @@
2848 uint32_t h = get_h();
2849 assert(labels.size());
2850 slider.set_pos(Vector2i(w / (2 * labels.size()) - slider.cursor_size_ / 2, 0));
2851- slider.set_size(
2852- w - (w / labels.size()) + slider.cursor_size_,
2853- h -
2854- UI::g_fh1->render(as_condensed(UI::g_fh1->fontset()->representative_character(),
2855- UI::Align::kLeft, UI_FONT_SIZE_SMALL - 2))
2856- ->height() +
2857- 2);
2858+ slider.set_size(w - (w / labels.size()) + slider.cursor_size_,
2859+ h - text_height(UI_FONT_SIZE_SMALL - 2, UI::FontSet::Face::kCondensed) + 2);
2860 Panel::layout();
2861 }
2862 }
2863
2864=== modified file 'src/ui_basic/table.cc'
2865--- src/ui_basic/table.cc 2017-05-14 14:40:24 +0000
2866+++ src/ui_basic/table.cc 2017-05-23 21:33:09 +0000
2867@@ -51,11 +51,8 @@
2868 TableRows rowtype)
2869 : Panel(parent, x, y, w, h),
2870 total_width_(0),
2871- headerheight_(
2872- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() +
2873- 4),
2874- lineheight_(
2875- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height()),
2876+ headerheight_(text_height() + 4),
2877+ lineheight_(text_height()),
2878 button_background_(button_background),
2879 scrollbar_(nullptr),
2880 scrollbar_filler_button_(
2881@@ -235,7 +232,7 @@
2882 Columns::size_type const nr_columns = columns_.size();
2883 for (uint32_t i = 0, curx = 0; i < nr_columns; ++i) {
2884 const Column& column = columns_[i];
2885- int const curw = column.width;
2886+ const int curw = column.width;
2887 Align alignment = mirror_alignment(column.alignment);
2888
2889 const Image* entry_picture = er.get_picture(i);
2890@@ -288,52 +285,37 @@
2891 }
2892 dst.blit(Vector2i(draw_x, point.y + (lineheight - pich) / 2), entry_picture);
2893 }
2894- point.x += picw;
2895+ if (alignment != Align::kRight) {
2896+ point.x += picw;
2897+ }
2898 }
2899
2900- ++picw; // A bit of margin between image and text
2901-
2902 if (entry_string.empty()) {
2903 curx += curw;
2904 continue;
2905 }
2906- const Image* entry_text_im = UI::g_fh1->render(as_uifont(richtext_escape(entry_string)));
2907-
2908+ std::shared_ptr<const UI::RenderedText> rendered_text =
2909+ UI::g_fh1->render(as_uifont(richtext_escape(entry_string)));
2910+
2911+ // Fix text alignment for BiDi languages if the entry contains an RTL character. We want
2912+ // this always on, e.g. for mixed language savegame filenames.
2913+ alignment = mirror_alignment(column.alignment, entry_string);
2914+
2915+ // Position the text according to alignment
2916 switch (alignment) {
2917 case UI::Align::kCenter:
2918 point.x += (curw - picw) / 2;
2919 break;
2920 case UI::Align::kRight:
2921- point.x += curw - 2 * picw;
2922+ point.x += curw - picw;
2923 break;
2924 case UI::Align::kLeft:
2925 break;
2926 }
2927
2928- // Add an offset for rightmost column when the scrollbar is shown.
2929- int text_width = entry_text_im->width();
2930- if (i == nr_columns - 1 && scrollbar_->is_enabled()) {
2931- text_width = text_width + scrollbar_->get_w();
2932- }
2933- UI::correct_for_align(alignment, text_width, &point);
2934-
2935- // Crop to column width while blitting
2936- if ((curw + picw) < text_width) {
2937- // Fix positioning for BiDi languages.
2938- if (UI::g_fh1->fontset()->is_rtl()) {
2939- point.x = (alignment == UI::Align::kRight) ? curx : curx + picw;
2940- }
2941- // We want this always on, e.g. for mixed language savegame filenames
2942- if (i18n::has_rtl_character(
2943- entry_string.c_str(), 20)) { // Restrict check for efficiency
2944- dst.blitrect(
2945- point, entry_text_im, Recti(text_width - curw + picw, 0, text_width, lineheight));
2946- } else {
2947- dst.blitrect(point, entry_text_im, Recti(0, 0, curw - picw, lineheight));
2948- }
2949- } else {
2950- dst.blitrect(point, entry_text_im, Recti(0, 0, curw - picw, lineheight));
2951- }
2952+ constexpr int kMargin = 1;
2953+ rendered_text->draw(dst, point, Recti(kMargin, 0, curw - picw - 2 * kMargin, lineheight),
2954+ alignment, RenderedText::CropMode::kSelf);
2955 curx += curw;
2956 }
2957
2958
2959=== modified file 'src/ui_basic/tabpanel.cc'
2960--- src/ui_basic/tabpanel.cc 2017-05-13 11:25:24 +0000
2961+++ src/ui_basic/tabpanel.cc 2017-05-23 21:33:09 +0000
2962@@ -47,19 +47,23 @@
2963 Tab::Tab(TabPanel* const tab_parent,
2964 size_t const tab_id,
2965 int32_t x,
2966- int32_t w,
2967 const std::string& name,
2968 const std::string& init_title,
2969 const Image* init_pic,
2970 const std::string& tooltip_text,
2971 Panel* const contents)
2972- : NamedPanel(tab_parent, name, x, 0, w, kTabPanelButtonHeight, tooltip_text),
2973+ : NamedPanel(tab_parent, name, x, 0, kTabPanelButtonHeight, kTabPanelButtonHeight, tooltip_text),
2974 parent(tab_parent),
2975 id(tab_id),
2976 pic(init_pic),
2977- title(init_title),
2978+ rendered_title(nullptr),
2979 tooltip(tooltip_text),
2980 panel(contents) {
2981+ if (!init_title.empty()) {
2982+ rendered_title = UI::g_fh1->render(as_uifont(init_title));
2983+ set_size(std::max(kTabPanelButtonHeight, rendered_title->width() + 2 * kTabPanelTextMargin),
2984+ kTabPanelButtonHeight);
2985+ }
2986 }
2987
2988 /**
2989@@ -165,9 +169,7 @@
2990 const std::string& title,
2991 Panel* const panel,
2992 const std::string& tooltip_text) {
2993- const Image* pic = UI::g_fh1->render(as_uifont(title));
2994- return add_tab(std::max(kTabPanelButtonHeight, pic->width() + 2 * kTabPanelTextMargin), name,
2995- title, pic, tooltip_text, panel);
2996+ return add_tab(name, title, nullptr, tooltip_text, panel);
2997 }
2998
2999 /**
3000@@ -177,12 +179,11 @@
3001 const Image* pic,
3002 Panel* const panel,
3003 const std::string& tooltip_text) {
3004- return add_tab(kTabPanelButtonHeight, name, "", pic, tooltip_text, panel);
3005+ return add_tab(name, "", pic, tooltip_text, panel);
3006 }
3007
3008 /** Common adding function for textual and pictorial tabs. */
3009-uint32_t TabPanel::add_tab(int32_t width,
3010- const std::string& name,
3011+uint32_t TabPanel::add_tab(const std::string& name,
3012 const std::string& title,
3013 const Image* pic,
3014 const std::string& tooltip_text,
3015@@ -192,7 +193,7 @@
3016
3017 size_t id = tabs_.size();
3018 int32_t x = id > 0 ? tabs_[id - 1]->get_x() + tabs_[id - 1]->get_w() : 0;
3019- tabs_.push_back(new Tab(this, id, x, width, name, title, pic, tooltip_text, panel));
3020+ tabs_.push_back(new Tab(this, id, x, name, title, pic, tooltip_text, panel));
3021
3022 // Add a margin if there is a border
3023 if (border_type_ == TabPanel::Type::kBorder) {
3024@@ -282,10 +283,8 @@
3025 dst.brighten_rect(Recti(x, 0, tab_width, kTabPanelButtonHeight), MOUSE_OVER_BRIGHT_FACTOR);
3026 }
3027
3028- assert(tabs_[idx]->pic);
3029-
3030- // If the title is empty, we will assume a pictorial tab
3031- if (tabs_[idx]->title.empty()) {
3032+ // If pic is there, we will assume a pictorial tab
3033+ if (tabs_[idx]->pic != nullptr) {
3034 // Scale the image down if needed, but keep the ratio.
3035 constexpr int kMaxImageSize = kTabPanelButtonHeight - 2 * kTabPanelImageMargin;
3036 double image_scale =
3037@@ -299,10 +298,10 @@
3038 (kTabPanelButtonHeight - picture_height) / 2, picture_width, picture_height),
3039 tabs_[idx]->pic, Recti(0, 0, tabs_[idx]->pic->width(), tabs_[idx]->pic->height()), 1,
3040 BlendMode::UseAlpha);
3041- } else {
3042- dst.blit(Vector2i(x + kTabPanelTextMargin,
3043- (kTabPanelButtonHeight - tabs_[idx]->pic->height()) / 2),
3044- tabs_[idx]->pic, BlendMode::UseAlpha);
3045+ } else if (tabs_[idx]->rendered_title != nullptr) {
3046+ tabs_[idx]->rendered_title->draw(
3047+ dst, Vector2i(x + kTabPanelTextMargin,
3048+ (kTabPanelButtonHeight - tabs_[idx]->rendered_title->height()) / 2));
3049 }
3050
3051 // Draw top part of border
3052
3053=== modified file 'src/ui_basic/tabpanel.h'
3054--- src/ui_basic/tabpanel.h 2017-02-12 09:10:57 +0000
3055+++ src/ui_basic/tabpanel.h 2017-05-23 21:33:09 +0000
3056@@ -46,7 +46,6 @@
3057 Tab(TabPanel* parent,
3058 size_t id,
3059 int32_t x,
3060- int32_t w,
3061 const std::string& name,
3062 const std::string& title,
3063 const Image* pic,
3064@@ -68,7 +67,7 @@
3065 uint32_t id;
3066
3067 const Image* pic;
3068- std::string title;
3069+ std::shared_ptr<const UI::RenderedText> rendered_title;
3070 std::string tooltip;
3071 Panel* panel;
3072 };
3073@@ -136,8 +135,7 @@
3074
3075 private:
3076 // Common adding function for textual and pictorial tabs
3077- uint32_t add_tab(int32_t width,
3078- const std::string& name,
3079+ uint32_t add_tab(const std::string& name,
3080 const std::string& title,
3081 const Image* pic,
3082 const std::string& tooltip,
3083
3084=== modified file 'src/ui_basic/textarea.cc'
3085--- src/ui_basic/textarea.cc 2017-04-22 10:17:39 +0000
3086+++ src/ui_basic/textarea.cc 2017-05-23 21:33:09 +0000
3087@@ -127,8 +127,7 @@
3088 if (!text_.empty()) {
3089 Vector2i anchor(
3090 (align_ == Align::kCenter) ? get_w() / 2 : (align_ == UI::Align::kRight) ? get_w() : 0, 0);
3091- UI::correct_for_align(align_, rendered_text_->width(), &anchor);
3092- dst.blit(anchor, rendered_text_, BlendMode::UseAlpha);
3093+ rendered_text_->draw(dst, anchor, align_);
3094 }
3095 }
3096
3097@@ -188,14 +187,12 @@
3098 uint32_t w = 0;
3099 uint16_t h = 0;
3100
3101- if (rendered_text_) {
3102+ if (rendered_text_.get()) {
3103 w = fixed_width_ > 0 ? fixed_width_ : rendered_text_->width();
3104 h = rendered_text_->height();
3105 // We want empty textareas to have height
3106 if (text_.empty()) {
3107- h = UI::g_fh1->render(
3108- as_uifont(UI::g_fh1->fontset()->representative_character(), fontsize_))
3109- ->height();
3110+ h = text_height(fontsize_);
3111 }
3112 }
3113 set_desired_size(w, h);
3114
3115=== modified file 'src/ui_basic/textarea.h'
3116--- src/ui_basic/textarea.h 2017-02-23 19:38:51 +0000
3117+++ src/ui_basic/textarea.h 2017-05-23 21:33:09 +0000
3118@@ -94,7 +94,7 @@
3119
3120 LayoutMode layoutmode_;
3121 std::string text_;
3122- const Image* rendered_text_;
3123+ std::shared_ptr<const UI::RenderedText> rendered_text_;
3124 Align align_;
3125 RGBColor color_;
3126 int fontsize_;
3127
3128=== modified file 'src/ui_basic/window.cc'
3129--- src/ui_basic/window.cc 2017-05-13 11:25:24 +0000
3130+++ src/ui_basic/window.cc 2017-05-23 21:33:09 +0000
3131@@ -272,14 +272,13 @@
3132 // draw the title if we have one
3133 if (!title_.empty()) {
3134 // The title shouldn't be richtext, but we escape it just to make sure.
3135- const Image* text =
3136+ std::shared_ptr<const UI::RenderedText> text =
3137 autofit_ui_text(richtext_escape(title_), get_inner_w(), UI_FONT_CLR_FG, 13);
3138
3139 // Blit on pixel boundary (not float), so that the text is blitted pixel perfect.
3140 Vector2i pos(get_lborder() + get_inner_w() / 2, TP_B_PIXMAP_THICKNESS / 2);
3141- UI::correct_for_align(UI::Align::kCenter, text->width(), &pos);
3142 UI::center_vertically(text->height(), &pos);
3143- dst.blit(pos, text, BlendMode::UseAlpha);
3144+ text->draw(dst, pos, UI::Align::kCenter);
3145 }
3146
3147 if (!is_minimal_) {
3148
3149=== modified file 'src/ui_fsmenu/loadgame.cc'
3150--- src/ui_fsmenu/loadgame.cc 2017-03-05 17:55:29 +0000
3151+++ src/ui_fsmenu/loadgame.cc 2017-05-23 21:33:09 +0000
3152@@ -332,19 +332,9 @@
3153 }
3154
3155 std::string FullscreenMenuLoadGame::filename_list_string() {
3156- std::set<uint32_t> selections = table_.selections();
3157 boost::format message;
3158- int counter = 0;
3159- for (const uint32_t index : selections) {
3160- ++counter;
3161- // TODO(GunChleoc): We can exceed the texture size for the font renderer,
3162- // so we have to restrict this for now.
3163- if (counter > 50) {
3164- message = boost::format("%s\n%s") % message % "...";
3165- break;
3166- }
3167+ for (const uint32_t index : table_.selections()) {
3168 const SavegameData& gamedata = games_data_[table_.get(table_.get_record(index))];
3169-
3170 if (gamedata.errormessage.empty()) {
3171 message =
3172 boost::format("%s\n%s") % message %
3173
3174=== modified file 'src/wui/chatoverlay.cc'
3175--- src/wui/chatoverlay.cc 2017-04-22 12:19:21 +0000
3176+++ src/wui/chatoverlay.cc 2017-05-23 21:33:09 +0000
3177@@ -167,15 +167,15 @@
3178 if (!m->havemessages_)
3179 return;
3180
3181- const Image* im = nullptr;
3182+ std::shared_ptr<const UI::RenderedText> im = std::shared_ptr<const UI::RenderedText>(nullptr);
3183 try {
3184 im = UI::g_fh1->render(m->all_text_, get_w());
3185 } catch (RT::WidthTooSmall&) {
3186 // Oops, maybe one long word? We render again, not limiting the width, but
3187 // render everything in one single line.
3188- im = UI::g_fh1->render(m->all_text_, 0);
3189+ im = UI::g_fh1->render(m->all_text_);
3190 }
3191- assert(im != nullptr);
3192+ assert(im.get() != nullptr);
3193
3194 // Background
3195 const int32_t height = im->height() > get_h() ? get_h() : im->height();
3196@@ -185,8 +185,6 @@
3197 if (!m->transparent_) {
3198 dst.fill_rect(Recti(0, top, width, height), RGBAColor(50, 50, 50, 128), BlendMode::Default);
3199 }
3200- int32_t topcrop = im->height() - height;
3201- Recti cropRect(0, topcrop, width, height);
3202-
3203- dst.blitrect(Vector2i(0, top), im, cropRect);
3204+ const int topcrop = im->height() - height;
3205+ im->draw(dst, Vector2i(0, top), Recti(0, topcrop, width, height));
3206 }
3207
3208=== modified file 'src/wui/game_tips.cc'
3209--- src/wui/game_tips.cc 2017-05-13 18:48:26 +0000
3210+++ src/wui/game_tips.cc 2017-05-23 21:33:09 +0000
3211@@ -99,20 +99,17 @@
3212 }
3213
3214 void GameTips::show_tip(int32_t index) {
3215- // try to load a background
3216+ RenderTarget& rt = *g_gr->get_render_target();
3217+
3218 const Image* pic_background = g_gr->images().get(BG_IMAGE);
3219- assert(pic_background);
3220-
3221- RenderTarget& rt = *g_gr->get_render_target();
3222-
3223- uint16_t w = pic_background->width();
3224- uint16_t h = pic_background->height();
3225+ const int w = pic_background->width();
3226+ const int h = pic_background->height();
3227 Vector2i pt((g_gr->get_xres() - w) / 2, (g_gr->get_yres() - h) / 2);
3228- Recti tips_area(pt, w, h);
3229 rt.blit(pt, pic_background);
3230
3231- const Image* rendered_text = UI::g_fh1->render(as_game_tip(tips_[index].text), tips_area.w);
3232- rt.blit(tips_area.center().cast<int>() -
3233- Vector2i(rendered_text->width() / 2, rendered_text->height() / 2),
3234- rendered_text);
3235+ std::shared_ptr<const UI::RenderedText> rendered_text =
3236+ UI::g_fh1->render(as_game_tip(tips_[index].text), w);
3237+ pt = Vector2i((g_gr->get_xres() - rendered_text->width()) / 2,
3238+ (g_gr->get_yres() - rendered_text->height()) / 2);
3239+ rendered_text->draw(rt, pt);
3240 }
3241
3242=== modified file 'src/wui/interactive_base.cc'
3243--- src/wui/interactive_base.cc 2017-05-14 14:40:24 +0000
3244+++ src/wui/interactive_base.cc 2017-05-23 21:33:09 +0000
3245@@ -356,8 +356,8 @@
3246 std::string node_text;
3247 if (is_game) {
3248 const std::string gametime(gametimestring(egbase().get_gametime(), true));
3249- const std::string gametime_text = as_condensed(gametime);
3250- dst.blit(Vector2i(5, 5), UI::g_fh1->render(gametime_text), BlendMode::UseAlpha);
3251+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_condensed(gametime));
3252+ rendered_text->draw(dst, Vector2i(5, 5));
3253
3254 static boost::format node_format("(%i, %i)");
3255 node_text = as_condensed((node_format % sel_.pos.node.x % sel_.pos.node.y).str());
3256@@ -366,20 +366,17 @@
3257 const int32_t height = map[sel_.pos.node].get_height();
3258 node_text = as_condensed((node_format % sel_.pos.node.x % sel_.pos.node.y % height).str());
3259 }
3260-
3261- const Image* rendered_text = UI::g_fh1->render(node_text);
3262- Vector2i point(get_w() - 5, get_h() - rendered_text->height() - 5);
3263- UI::correct_for_align(UI::Align::kRight, rendered_text->width(), &point);
3264- dst.blit(point, rendered_text, BlendMode::UseAlpha);
3265+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(node_text);
3266+ rendered_text->draw(
3267+ dst, Vector2i(get_w() - 5, get_h() - rendered_text->height() - 5), UI::Align::kRight);
3268 }
3269
3270 // Blit FPS when playing a game in debug mode.
3271 if (get_display_flag(dfDebug) && is_game) {
3272 static boost::format fps_format("%5.1f fps (avg: %5.1f fps)");
3273- const Image* rendered_text = UI::g_fh1->render(as_condensed(
3274+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_condensed(
3275 (fps_format % (1000.0 / frametime_) % (1000.0 / (avg_usframetime_ / 1000))).str()));
3276- dst.blit(
3277- Vector2i((get_w() - rendered_text->width()) / 2, 5), rendered_text, BlendMode::UseAlpha);
3278+ rendered_text->draw(dst, Vector2i((get_w() - rendered_text->width()) / 2, 5));
3279 }
3280 }
3281
3282
3283=== modified file 'src/wui/interactive_base.h'
3284--- src/wui/interactive_base.h 2017-03-03 18:13:55 +0000
3285+++ src/wui/interactive_base.h 2017-05-23 21:33:09 +0000
3286@@ -29,11 +29,11 @@
3287 #include "logic/map.h"
3288 #include "notifications/notifications.h"
3289 #include "profile/profile.h"
3290+#include "sound/note_sound.h"
3291+#include "sound/sound_handler.h"
3292 #include "ui_basic/box.h"
3293 #include "ui_basic/textarea.h"
3294 #include "ui_basic/unique_window.h"
3295-#include "sound/note_sound.h"
3296-#include "sound/sound_handler.h"
3297 #include "wui/chatoverlay.h"
3298 #include "wui/debugconsole.h"
3299 #include "wui/edge_overlay_manager.h"
3300
3301=== modified file 'src/wui/interactive_gamebase.cc'
3302--- src/wui/interactive_gamebase.cc 2017-04-22 10:17:39 +0000
3303+++ src/wui/interactive_gamebase.cc 2017-05-23 21:33:09 +0000
3304@@ -126,10 +126,8 @@
3305 }
3306
3307 if (!game_speed.empty()) {
3308- Vector2i point(get_w() - 5, 5);
3309- const Image* rendered_speed = UI::g_fh1->render(game_speed);
3310- UI::correct_for_align(UI::Align::kRight, rendered_speed->width(), &point);
3311- dst.blit(point, rendered_speed, BlendMode::UseAlpha);
3312+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(game_speed);
3313+ rendered_text->draw(dst, Vector2i(get_w() - 5, 5), UI::Align::kRight);
3314 }
3315 }
3316 }
3317
3318=== modified file 'src/wui/mapdetails.cc'
3319--- src/wui/mapdetails.cc 2017-02-23 17:58:25 +0000
3320+++ src/wui/mapdetails.cc 2017-05-23 21:33:09 +0000
3321@@ -102,9 +102,7 @@
3322 }
3323
3324 void MapDetails::layout() {
3325- name_label_.set_size(
3326- get_w() - padding_,
3327- UI::g_fh1->render(as_uifont(UI::g_fh1->fontset()->representative_character()))->height() + 2);
3328+ name_label_.set_size(get_w() - padding_, text_height() + 2);
3329
3330 // Adjust sizes for show / hide suggested teams
3331 if (suggested_teams_box_->is_visible()) {
3332
3333=== modified file 'src/wui/maptable.cc'
3334--- src/wui/maptable.cc 2017-03-05 17:55:29 +0000
3335+++ src/wui/maptable.cc 2017-05-23 21:33:09 +0000
3336@@ -33,7 +33,7 @@
3337 add_column(35, _("Pl."), _("Number of players"), UI::Align::kCenter);
3338 add_column(0, _("Filename"), _("The name of the map or scenario"), UI::Align::kLeft,
3339 UI::TableColumnType::kFlexible);
3340- add_column(115, _("Size"), _("The size of the map (Width x Height)"));
3341+ add_column(90, _("Size"), _("The size of the map (Width x Height)"));
3342 set_sort_column(0);
3343 }
3344
3345
3346=== modified file 'src/wui/plot_area.cc'
3347--- src/wui/plot_area.cc 2017-05-13 13:14:29 +0000
3348+++ src/wui/plot_area.cc 2017-05-23 21:33:09 +0000
3349@@ -178,17 +178,15 @@
3350 const RGBColor& color,
3351 const Vector2i& pos,
3352 RenderTarget& dst) {
3353- const Image* pic = UI::g_fh1->render(ytick_text_style(value, color));
3354+ std::shared_ptr<const UI::RenderedText> tick = UI::g_fh1->render(ytick_text_style(value, color));
3355 Vector2i point(pos); // Un-const this
3356- UI::correct_for_align(UI::Align::kRight, pic->width(), &point);
3357- UI::center_vertically(pic->height(), &point);
3358- dst.blit(point, pic, BlendMode::UseAlpha);
3359+ UI::center_vertically(tick->height(), &point);
3360+ tick->draw(dst, point, UI::Align::kRight);
3361 }
3362
3363 uint32_t calc_plot_x_max_ticks(int32_t plot_width) {
3364 // Render a number with 3 digits (maximal length which should appear)
3365- const Image* pic = UI::g_fh1->render(ytick_text_style(" -888 ", kAxisLineColor));
3366- return plot_width / pic->width();
3367+ return plot_width / UI::g_fh1->render(ytick_text_style(" -888 ", kAxisLineColor))->width();
3368 }
3369
3370 int calc_slider_label_width(const std::string& label) {
3371@@ -264,12 +262,11 @@
3372
3373 // The space at the end is intentional to have the tick centered
3374 // over the number, not to the left
3375- const Image* xtick = UI::g_fh1->render(
3376+ std::shared_ptr<const UI::RenderedText> xtick = UI::g_fh1->render(
3377 xtick_text_style((boost::format("-%u ") % (max_x / how_many_ticks * i)).str()));
3378 Vector2i pos(posx, inner_h - kSpaceBottom + 10);
3379- UI::correct_for_align(UI::Align::kCenter, xtick->width(), &pos);
3380 UI::center_vertically(xtick->height(), &pos);
3381- dst.blit(pos, xtick, BlendMode::UseAlpha);
3382+ xtick->draw(dst, pos, UI::Align::kCenter);
3383
3384 posx -= sub;
3385 }
3386@@ -284,10 +281,10 @@
3387 kAxisLineColor, kAxisLinesWidth);
3388
3389 // print the used unit
3390- const Image* xtick = UI::g_fh1->render(xtick_text_style(get_generic_unit_name(unit)));
3391+ std::shared_ptr<const UI::RenderedText> xtick = UI::g_fh1->render(xtick_text_style(get_generic_unit_name(unit)));
3392 Vector2i pos(2, kSpacing + 2);
3393 UI::center_vertically(xtick->height(), &pos);
3394- dst.blit(pos, xtick, BlendMode::UseAlpha);
3395+ xtick->draw(dst, pos);
3396 }
3397
3398 } // namespace
3399
3400=== modified file 'src/wui/waresdisplay.cc'
3401--- src/wui/waresdisplay.cc 2017-05-13 13:14:29 +0000
3402+++ src/wui/waresdisplay.cc 2017-05-23 21:33:09 +0000
3403@@ -331,11 +331,10 @@
3404 dst.fill_rect(Recti(p + Vector2i(0, WARE_MENU_PIC_HEIGHT), w, WARE_MENU_INFO_SIZE),
3405 info_color_for_ware(id));
3406
3407- const Image* text = UI::g_fh1->render(as_waresinfo(info_for_ware(id)));
3408- if (text) // might be zero when there is no info text.
3409- dst.blit(p + Vector2i(w - text->width() - 1,
3410- WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + 1 - text->height()),
3411- text);
3412+ std::shared_ptr<const UI::RenderedText> rendered_text = UI::g_fh1->render(as_waresinfo(info_for_ware(id)));
3413+ rendered_text->draw(
3414+ dst, Vector2i(p.x + w - rendered_text->width() - 1,
3415+ p.y + WARE_MENU_PIC_HEIGHT + WARE_MENU_INFO_SIZE + 1 - rendered_text->height()));
3416 }
3417
3418 // Wares highlighting/selecting

Subscribers

People subscribed via source and target branches

to status/vote changes: