Merge lp:~widelands-dev/widelands/rtl_wordwrap into lp:widelands

Proposed by GunChleoc
Status: Merged
Merged at revision: 7676
Proposed branch: lp:~widelands-dev/widelands/rtl_wordwrap
Merge into: lp:widelands
Diff against target: 793 lines (+294/-98)
12 files modified
src/graphic/text/bidi.cc (+18/-2)
src/graphic/text/bidi.h (+12/-10)
src/graphic/text/rt_parse.cc (+6/-1)
src/graphic/text/rt_render.cc (+21/-7)
src/graphic/text_layout.cc (+33/-0)
src/graphic/text_layout.h (+8/-0)
src/graphic/wordwrap.cc (+143/-49)
src/graphic/wordwrap.h (+15/-3)
src/ui_basic/messagebox.cc (+5/-4)
src/ui_basic/multilineeditbox.cc (+4/-1)
src/ui_basic/multilinetextarea.cc (+28/-20)
src/ui_basic/multilinetextarea.h (+1/-1)
To merge this branch: bzr merge lp:~widelands-dev/widelands/rtl_wordwrap
Reviewer Review Type Date Requested Status
TiborB Approve
GunChleoc Needs Resubmitting
Review via email: mp+273968@code.launchpad.net

Description of the change

Wordwrap now uses the new font renderer, which fixes positioning bugs for Right-to-left languages. I also implemented the line break rules for Japanese into WordWrap. You can see it in action e.g. in the map descriptions when selecting a map for a new game.

TextLayout now has a new estimate function (calc_width_for_wrapping) for glyph width, as the old one was too inaccurate for estimating the width of the lines for Arabic. We still need an estimate function rather than getting the exact width, because rendering the textures just for an estimate is way too slow (takes about 100x longer). The old function (calc_bare_width) is still used in RichText for now, so I have left it in.

There is one bug left in the MultilineEditbox for Arabic - the cursor isn't shown in the correct position. You can see this bug when editing the Map Options. Everything should be fine for RTL languages like Latin script though. I have added a TODO comment for this bug.

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

Not good, end of lines in map description are cut. I will send you and screenshot in email...

review: Needs Fixing
Revision history for this message
GunChleoc (gunchleoc) wrote :

The text truncation problem should be fixed now, at the expense of efficiency. I am planning to use the word_wrap algorithm in editboxes only, so the efficiency won't be that much of a problem. I would like to keep it in the multiline-textarea for a bit though, so it will get more testing exposure.

Note that the caret rendering in the ediboxes for RTL languages is broken, but it didn't work before either, so it's not a regression.

The best test case is the map descriptions when loading a new map. This should be tested at various screen resolutions with our own language, English, Arabic and Jaoanese (= Nihongo, that's the language entry below Nederlands). You should also be able to navigate around text and type in the Editor Map Options like before.

review: Needs Resubmitting
Revision history for this message
TiborB (tiborb95) wrote :

It seems to be OK now

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/graphic/text/bidi.cc'
--- src/graphic/text/bidi.cc 2015-10-11 15:00:53 +0000
+++ src/graphic/text/bidi.cc 2015-12-28 21:49:42 +0000
@@ -22,10 +22,10 @@
22#include <map>22#include <map>
23#include <string>23#include <string>
2424
25#include <unicode/unistr.h>
26#include <unicode/utypes.h>25#include <unicode/utypes.h>
2726
28#include "base/log.h"27#include "base/log.h"
28#include "graphic/text/font_set.h"
2929
30namespace {30namespace {
31// TODO(GunChleoc): Have a look at the ICU API to see which helper functions can be gained from there.31// TODO(GunChleoc): Have a look at the ICU API to see which helper functions can be gained from there.
@@ -710,6 +710,17 @@
710 return result;710 return result;
711}711}
712712
713// Helper to convert ICU strings to C++ strings
714std::string icustring2string(const icu::UnicodeString& convertme) {
715 std::string result;
716 convertme.toUTF8String(result);
717 return result;
718}
719std::string icuchar2string(const UChar& convertme) {
720 const icu::UnicodeString temp(convertme);
721 return icustring2string(temp);
722}
723
713// True if a string contains a character from a CJK code block724// True if a string contains a character from a CJK code block
714bool has_cjk_character(const char* input) {725bool has_cjk_character(const char* input) {
715 bool result = false;726 bool result = false;
@@ -745,11 +756,16 @@
745}756}
746757
747bool cannot_start_line(const UChar& c) {758bool cannot_start_line(const UChar& c) {
748 return kCannottStartLineJapanese.count(c) == 1;759 return is_diacritic(c) || is_punctuation_char(c) || kCannottStartLineJapanese.count(c) == 1;
749}760}
750761
751bool cannot_end_line(const UChar& c) {762bool cannot_end_line(const UChar& c) {
752 return kCannotEndLineJapanese.count(c) == 1;763 return kCannotEndLineJapanese.count(c) == 1;
753}764}
754765
766bool is_diacritic(const UChar& c) {
767 return kArabicDiacritics.count(c) == 1;
768}
769
770
755} // namespace UI771} // namespace UI
756772
=== modified file 'src/graphic/text/bidi.h'
--- src/graphic/text/bidi.h 2015-09-28 18:47:57 +0000
+++ src/graphic/text/bidi.h 2015-12-28 21:49:42 +0000
@@ -24,19 +24,21 @@
24#include <vector>24#include <vector>
2525
26#include <unicode/uchar.h>26#include <unicode/uchar.h>
2727#include <unicode/unistr.h>
28#include "graphic/text/font_set.h"
2928
30// BiDi support for RTL languages29// BiDi support for RTL languages
31namespace i18n {30namespace i18n {
32 std::string make_ligatures(const char* input);31std::string make_ligatures(const char* input);
33 std::string line2bidi(const char* input);32std::string line2bidi(const char* input);
34 std::vector<std::string> split_cjk_word(const char* input);33std::vector<std::string> split_cjk_word(const char* input);
35 bool has_rtl_character(const char* input);34bool has_rtl_character(const char* input);
36 bool has_rtl_character(std::vector<std::string> input);35bool has_rtl_character(std::vector<std::string> input);
37 bool has_cjk_character(const char* input);36std::string icustring2string(const UnicodeString& convertme);
38 bool cannot_start_line(const UChar& c);37std::string icuchar2string(const UChar& convertme);
39 bool cannot_end_line(const UChar& c);38bool has_cjk_character(const char* input);
39bool cannot_start_line(const UChar& c);
40bool cannot_end_line(const UChar& c);
41bool is_diacritic(const UChar& c);
4042
41} // namespace UI43} // namespace UI
4244
4345
=== modified file 'src/graphic/text/rt_parse.cc'
--- src/graphic/text/rt_parse.cc 2014-09-09 17:15:20 +0000
+++ src/graphic/text/rt_parse.cc 2015-12-28 21:49:42 +0000
@@ -25,6 +25,7 @@
25#include <vector>25#include <vector>
2626
27#include <SDL.h>27#include <SDL.h>
28#include <boost/algorithm/string.hpp>
28#include <boost/format.hpp>29#include <boost/format.hpp>
2930
30#include "graphic/text/rt_errors_impl.h"31#include "graphic/text/rt_errors_impl.h"
@@ -138,8 +139,11 @@
138 size_t line = ts.line(), col = ts.col();139 size_t line = ts.line(), col = ts.col();
139 std::string text = ts.till_any("<");140 std::string text = ts.till_any("<");
140 if (text != "") {141 if (text != "") {
141 if (!tc.text_allowed)142 if (!tc.text_allowed) {
142 throw SyntaxErrorImpl(line, col, "no text, as only tags are allowed here", text, ts.peek(100));143 throw SyntaxErrorImpl(line, col, "no text, as only tags are allowed here", text, ts.peek(100));
144 }
145 boost::replace_all(text, "&gt;", ">");
146 boost::replace_all(text, "&lt;", "<");
143 m_childs.push_back(new Child(text));147 m_childs.push_back(new Child(text));
144 }148 }
145149
@@ -181,6 +185,7 @@
181 tc.allowed_attrs.insert("padding_b");185 tc.allowed_attrs.insert("padding_b");
182 tc.allowed_attrs.insert("padding_t");186 tc.allowed_attrs.insert("padding_t");
183 tc.allowed_attrs.insert("db_show_spaces");187 tc.allowed_attrs.insert("db_show_spaces");
188 tc.allowed_attrs.insert("keep_spaces"); // Keeps blank spaces intact for text editing
184 tc.allowed_attrs.insert("background");189 tc.allowed_attrs.insert("background");
185190
186 tc.allowed_childs.insert("p");191 tc.allowed_childs.insert("p");
187192
=== modified file 'src/graphic/text/rt_render.cc'
--- src/graphic/text/rt_render.cc 2015-09-28 18:47:57 +0000
+++ src/graphic/text/rt_render.cc 2015-12-28 21:49:42 +0000
@@ -400,7 +400,7 @@
400 Texture* render(TextureCache* texture_cache) override {400 Texture* render(TextureCache* texture_cache) override {
401 if (m_show_spaces) {401 if (m_show_spaces) {
402 Texture* rv = new Texture(m_w, m_h);402 Texture* rv = new Texture(m_w, m_h);
403 fill_rect(Rect(0, 0, m_w, m_h), RGBAColor(0xff, 0, 0, 0xff), rv);403 fill_rect(Rect(0, 0, m_w, m_h), RGBAColor(0xcc, 0, 0, 0xcc), rv);
404 return rv;404 return rv;
405 }405 }
406 return TextNode::render(texture_cache);406 return TextNode::render(texture_cache);
@@ -711,17 +711,26 @@
711 if (i18n::has_rtl_character(txt.c_str())) {711 if (i18n::has_rtl_character(txt.c_str())) {
712 std::string previous_word;712 std::string previous_word;
713 std::vector<RenderNode*>::iterator it = text_nodes.begin();713 std::vector<RenderNode*>::iterator it = text_nodes.begin();
714 std::vector<WordSpacerNode*> spacer_nodes;
714715
715 // Collect the word nodes716 // Collect the word nodes
716 while (ts.pos() < txt.size()) {717 while (ts.pos() < txt.size()) {
718 std::size_t cpos = ts.pos();
717 ts.skip_ws();719 ts.skip_ws();
720 spacer_nodes.clear();
721
722 // We only know if the spacer goes to the left or right after having a look at the current word.
723 for (uint16_t ws_indx = 0; ws_indx < ts.pos() - cpos; ws_indx++) {
724 spacer_nodes.push_back(new WordSpacerNode(font_cache_.get_font(&ns), ns));
725 }
726
718 word = ts.till_any_or_end(" \t\n\r");727 word = ts.till_any_or_end(" \t\n\r");
719 if (!word.empty()) {728 if (!word.empty()) {
720 bool word_is_bidi = i18n::has_rtl_character(word.c_str());729 bool word_is_bidi = i18n::has_rtl_character(word.c_str());
721 word = i18n::make_ligatures(word.c_str());730 word = i18n::make_ligatures(word.c_str());
722 if (word_is_bidi || i18n::has_rtl_character(previous_word.c_str())) {731 if (word_is_bidi || i18n::has_rtl_character(previous_word.c_str())) {
723 if (!previous_word.empty()) {732 for (WordSpacerNode* spacer: spacer_nodes) {
724 text_nodes.insert(text_nodes.begin(), new WordSpacerNode(font_cache_.get_font(&ns), ns));733 it = text_nodes.insert(text_nodes.begin(), spacer);
725 }734 }
726 if (word_is_bidi) {735 if (word_is_bidi) {
727 word = i18n::line2bidi(word.c_str());736 word = i18n::line2bidi(word.c_str());
@@ -732,8 +741,11 @@
732 if (it < text_nodes.end()) {741 if (it < text_nodes.end()) {
733 ++it;742 ++it;
734 }743 }
735 if (!previous_word.empty()) {744 for (WordSpacerNode* spacer: spacer_nodes) {
736 it = text_nodes.insert(it, new WordSpacerNode(font_cache_.get_font(&ns), ns));745 it = text_nodes.insert(it, spacer);
746 if (it < text_nodes.end()) {
747 ++it;
748 }
737 }749 }
738 it = text_nodes.insert(it, new TextNode(font_cache_.get_font(&ns), ns, word));750 it = text_nodes.insert(it, new TextNode(font_cache_.get_font(&ns), ns, word));
739 }751 }
@@ -749,7 +761,7 @@
749 while (ts.pos() < txt.size()) {761 while (ts.pos() < txt.size()) {
750 std::size_t cpos = ts.pos();762 std::size_t cpos = ts.pos();
751 ts.skip_ws();763 ts.skip_ws();
752 if (ts.pos() != cpos) {764 for (uint16_t ws_indx = 0; ws_indx < ts.pos() - cpos; ws_indx++) {
753 nodes.push_back(new WordSpacerNode(font_cache_.get_font(&ns), ns));765 nodes.push_back(new WordSpacerNode(font_cache_.get_font(&ns), ns));
754 }766 }
755 word = ts.till_any_or_end(" \t\n\r");767 word = ts.till_any_or_end(" \t\n\r");
@@ -1072,8 +1084,9 @@
1072 else if (align == "center" || align == "middle") m_rn->set_valign(UI::Align::Align_Center);1084 else if (align == "center" || align == "middle") m_rn->set_valign(UI::Align::Align_Center);
1073 }1085 }
1074 }1086 }
1075private:1087protected:
1076 bool shrink_to_fit_;1088 bool shrink_to_fit_;
1089private:
1077 uint16_t m_w;1090 uint16_t m_w;
1078 SubTagRenderNode* m_rn;1091 SubTagRenderNode* m_rn;
1079};1092};
@@ -1089,6 +1102,7 @@
1089 void handle_unique_attributes() override {1102 void handle_unique_attributes() override {
1090 const AttrMap& a = m_tag.attrs();1103 const AttrMap& a = m_tag.attrs();
1091 WordSpacerNode::show_spaces(a.has("db_show_spaces") ? a["db_show_spaces"].get_bool() : 0);1104 WordSpacerNode::show_spaces(a.has("db_show_spaces") ? a["db_show_spaces"].get_bool() : 0);
1105 shrink_to_fit_ = shrink_to_fit_ && (a.has("keep_spaces") ? !a["keep_spaces"].get_bool() : true);
1092 }1106 }
1093};1107};
10941108
10951109
=== modified file 'src/graphic/text_layout.cc'
--- src/graphic/text_layout.cc 2015-09-24 18:45:27 +0000
+++ src/graphic/text_layout.cc 2015-12-28 21:49:42 +0000
@@ -22,6 +22,7 @@
22#include <map>22#include <map>
2323
24#include <SDL_ttf.h>24#include <SDL_ttf.h>
25#include <boost/algorithm/string.hpp>
25#include <boost/format.hpp>26#include <boost/format.hpp>
2627
27#include "base/utf8.h"28#include "base/utf8.h"
@@ -30,6 +31,13 @@
30#include "graphic/text/font_set.h"31#include "graphic/text/font_set.h"
31#include "graphic/text_constants.h"32#include "graphic/text_constants.h"
3233
34std::string richtext_escape(const std::string& given_text) {
35 std::string text = given_text;
36 boost::replace_all(text, ">", "&gt;");
37 boost::replace_all(text, "<", "&lt;");
38 return text;
39}
40
33std::string as_game_tip(const std::string& txt) {41std::string as_game_tip(const std::string& txt) {
34 static boost::format f42 static boost::format f
35 ("<rt padding_l=48 padding_t=28 padding_r=48 padding_b=28>"43 ("<rt padding_l=48 padding_t=28 padding_r=48 padding_b=28>"
@@ -94,6 +102,31 @@
94}102}
95103
96/**104/**
105 * Get a width estimate for text wrapping.
106 */
107uint32_t TextStyle::calc_width_for_wrapping(const UChar& c) const {
108 int result = 0;
109 TTF_GlyphMetrics(font->get_ttf_font(), c, nullptr, nullptr, nullptr, nullptr, &result);
110 return result;
111}
112
113/**
114 * Get a width estimate for text wrapping.
115 */
116uint32_t TextStyle::calc_width_for_wrapping(const std::string & text) const
117{
118 int result = 0;
119 const icu::UnicodeString parseme(text.c_str());
120 for (int i = 0; i < parseme.length(); ++i) {
121 UChar c = parseme.charAt(i);
122 if (!i18n::is_diacritic(c)) {
123 result += calc_width_for_wrapping(c);
124 }
125 }
126 return result;
127}
128
129/**
97 * Compute the bare width (without caret padding) of the given string.130 * Compute the bare width (without caret padding) of the given string.
98 */131 */
99uint32_t TextStyle::calc_bare_width(const std::string & text) const132uint32_t TextStyle::calc_bare_width(const std::string & text) const
100133
=== modified file 'src/graphic/text_layout.h'
--- src/graphic/text_layout.h 2014-12-06 12:22:35 +0000
+++ src/graphic/text_layout.h 2015-12-28 21:49:42 +0000
@@ -21,6 +21,7 @@
21#define WL_GRAPHIC_TEXT_LAYOUT_H21#define WL_GRAPHIC_TEXT_LAYOUT_H
2222
23#include <string>23#include <string>
24#include <unicode/uchar.h>
2425
25#include "graphic/font.h"26#include "graphic/font.h"
26#include "graphic/color.h"27#include "graphic/color.h"
@@ -34,6 +35,11 @@
34}35}
3536
36/**37/**
38 * Escapes reserved characters for Richtext.
39 */
40std::string richtext_escape(const std::string& given_text);
41
42/**
37 * Convenience functions to convert simple text into a valid block43 * Convenience functions to convert simple text into a valid block
38 * of rich text which can be rendered.44 * of rich text which can be rendered.
39 */45 */
@@ -71,6 +77,8 @@
71 static const TextStyle & ui_big();77 static const TextStyle & ui_big();
72 static const TextStyle & ui_small();78 static const TextStyle & ui_small();
73 uint32_t calc_bare_width(const std::string & text) const;79 uint32_t calc_bare_width(const std::string & text) const;
80 uint32_t calc_width_for_wrapping(const UChar& c) const;
81 uint32_t calc_width_for_wrapping(const std::string & text) const;
74 void calc_bare_height_heuristic(const std::string & text, int32_t & miny, int32_t & maxy) const;82 void calc_bare_height_heuristic(const std::string & text, int32_t & miny, int32_t & maxy) const;
75 void setup() const;83 void setup() const;
7684
7785
=== modified file 'src/graphic/wordwrap.cc'
--- src/graphic/wordwrap.cc 2015-09-28 06:41:58 +0000
+++ src/graphic/wordwrap.cc 2015-12-28 21:49:42 +0000
@@ -23,12 +23,42 @@
2323
24#include "graphic/wordwrap.h"24#include "graphic/wordwrap.h"
2525
26#include <boost/format.hpp>
27#include <unicode/uchar.h>
28#include <unicode/unistr.h>
29
26#include "base/log.h"30#include "base/log.h"
27#include "graphic/font_handler.h"
28#include "graphic/font_handler1.h"31#include "graphic/font_handler1.h"
32#include "graphic/graphic.h"
29#include "graphic/rendertarget.h"33#include "graphic/rendertarget.h"
30#include "graphic/text/bidi.h"34#include "graphic/text/bidi.h"
3135
36namespace {
37std::string as_editorfont(const std::string& text,
38 int ptsize = UI_FONT_SIZE_SMALL,
39 const RGBColor& clr = UI_FONT_CLR_FG) {
40 // UI Text is always bold due to historic reasons
41 static boost::format
42 f("<rt keep_spaces=1><p><font face=serif size=%i bold=1 shadow=1 color=%s>%s</font></p></rt>");
43 f % ptsize;
44 f % clr.hex_value();
45 f % richtext_escape(text);
46 return f.str();
47}
48
49// This is inefficient; only call when we need the exact width.
50uint32_t text_width(const std::string& text, int ptsize) {
51 return UI::g_fh1->render(as_editorfont(text, ptsize - UI::g_fh1->fontset().size_offset()))->width();
52}
53
54// This is inefficient; only call when we need the exact height.
55uint32_t text_height(const std::string& text, int ptsize) {
56 return UI::g_fh1->render(as_editorfont(text.empty() ? "." : text,
57 ptsize - UI::g_fh1->fontset().size_offset()))->height();
58}
59
60} // namespace
61
32namespace UI {62namespace UI {
3363
34/**64/**
@@ -36,12 +66,12 @@
36 * and a default-constructed text style.66 * and a default-constructed text style.
37 */67 */
38WordWrap::WordWrap() :68WordWrap::WordWrap() :
39 m_wrapwidth(std::numeric_limits<uint32_t>::max())69 m_wrapwidth(std::numeric_limits<uint32_t>::max()), m_draw_caret(false), mode_(WordWrap::Mode::kDisplay)
40{70{
41}71}
4272
43WordWrap::WordWrap(const TextStyle & style, uint32_t gwrapwidth) :73WordWrap::WordWrap(const TextStyle & style, uint32_t gwrapwidth) :
44 m_style(style)74 m_style(style), m_draw_caret(false), mode_(WordWrap::Mode::kDisplay)
45{75{
46 m_wrapwidth = gwrapwidth;76 m_wrapwidth = gwrapwidth;
4777
@@ -82,20 +112,19 @@
82 * Perform the wrapping computations for the given text and fill in112 * Perform the wrapping computations for the given text and fill in
83 * the private data containing the wrapped results.113 * the private data containing the wrapped results.
84 */114 */
85void WordWrap::wrap(const std::string & text)115void WordWrap::wrap(const std::string & text, WordWrap::Mode mode)
86{116{
87 //static int count = 0;117 mode_ = mode;
88 //log("word_wrap_text(%u): %i\n", m_wrapwidth, ++count);
89
90 m_lines.clear();118 m_lines.clear();
91119
92 std::string::size_type line_start = 0;120 std::string::size_type line_start = 0;
121 uint32_t margin = m_style.calc_width_for_wrapping(0x2003); // Em space
93122
94 while (line_start <= text.size()) {123 while (line_start <= text.size()) {
95 std::string::size_type next_line_start;124 std::string::size_type next_line_start;
96 std::string::size_type line_end;125 std::string::size_type line_end;
97126
98 compute_end_of_line(text, line_start, line_end, next_line_start);127 compute_end_of_line(text, line_start, line_end, next_line_start, margin);
99128
100 LineData ld;129 LineData ld;
101 ld.start = line_start;130 ld.start = line_start;
@@ -115,15 +144,19 @@
115 (const std::string & text,144 (const std::string & text,
116 std::string::size_type line_start,145 std::string::size_type line_start,
117 std::string::size_type & line_end,146 std::string::size_type & line_end,
118 std::string::size_type & next_line_start)147 std::string::size_type & next_line_start,
148 uint32_t safety_margin)
119{149{
150 std::string::size_type minimum_chars = 1; // So we won't get empty lines
151 assert(m_wrapwidth > safety_margin);
152
120 std::string::size_type orig_end = text.find('\n', line_start);153 std::string::size_type orig_end = text.find('\n', line_start);
121 if (orig_end == std::string::npos)154 if (orig_end == std::string::npos)
122 orig_end = text.size();155 orig_end = text.size();
123156
124 if (m_wrapwidth == std::numeric_limits<uint32_t>::max() || orig_end - line_start <= 1) {157 if (m_wrapwidth == std::numeric_limits<uint32_t>::max() || orig_end - line_start <= minimum_chars) {
125 // Special fast path when wrapping is disabled or158 // Special fast path when wrapping is disabled or
126 // original text line contains at most one character159 // original text line contains at most minimum_chars characters
127 line_end = orig_end;160 line_end = orig_end;
128 next_line_start = orig_end + 1;161 next_line_start = orig_end + 1;
129 return;162 return;
@@ -131,9 +164,8 @@
131164
132165
133 // Optimism: perhaps the entire line fits?166 // Optimism: perhaps the entire line fits?
134 // TODO(GunChleoc): Arabic: Multiple calls of make_ligatures are inefficient.167 if (text_width(text.substr(line_start, orig_end - line_start), m_style.font->size())
135 if (m_style.calc_bare_width(i18n::make_ligatures(text.substr(line_start, orig_end - line_start).c_str()))168 <= m_wrapwidth - safety_margin) {
136 <= m_wrapwidth) {
137 line_end = orig_end;169 line_end = orig_end;
138 next_line_start = orig_end + 1;170 next_line_start = orig_end + 1;
139 return;171 return;
@@ -150,8 +182,7 @@
150 while (end_upper - end_lower > 4) {182 while (end_upper - end_lower > 4) {
151 std::string::size_type mid = end_lower + (end_upper - end_lower + 1) / 2;183 std::string::size_type mid = end_lower + (end_upper - end_lower + 1) / 2;
152184
153 if (m_style.calc_bare_width(i18n::make_ligatures(text.substr(line_start, mid - line_start).c_str()))185 if (line_fits(i18n::make_ligatures(text.substr(line_start, mid - line_start).c_str()), safety_margin)) {
154 <= m_wrapwidth) {
155 end_lower = mid;186 end_lower = mid;
156 } else {187 } else {
157 end_upper = mid - 1;188 end_upper = mid - 1;
@@ -171,11 +202,10 @@
171 break; // we already know that this cannot possibly fit202 break; // we already know that this cannot possibly fit
172203
173 // check whether the next word still fits204 // check whether the next word still fits
174 if (m_style.calc_bare_width(205 if (!line_fits(i18n::make_ligatures(text.substr(line_start, nextspace - line_start).c_str()),
175 i18n::make_ligatures(text.substr(line_start, nextspace - line_start).c_str()))206 safety_margin)) {
176 > m_wrapwidth)
177 break;207 break;
178208 }
179 space = nextspace;209 space = nextspace;
180 }210 }
181211
@@ -185,20 +215,62 @@
185 return;215 return;
186 }216 }
187217
188 // Nasty special case: the line starts with a single word that is too big to fit218
189 // Continue the binary search until we narrowed down exactly how many characters fit219 // The line didn't fit.
190 while (end_upper > end_lower) {220 // We just do a linear search ahead until we hit the max.
191 std::string::size_type mid = end_lower + (end_upper - end_lower + 1) / 2;221 const icu::UnicodeString unicode_word(text.substr(line_start, orig_end).c_str());
192222 uint32_t line_width = 0;
193 if (m_style.calc_bare_width(i18n::make_ligatures(text.substr(line_start, mid - line_start).c_str()))223 int32_t end = -1;
194 <= m_wrapwidth) {224 icu::UnicodeString unicode_line;
195 end_lower = mid;225
226 while ((line_width < (m_wrapwidth - safety_margin)) && (end < unicode_word.length())) {
227 ++end;
228 UChar c = unicode_word.charAt(end);
229 // Diacritics do not add to the line width
230 if (!i18n::is_diacritic(c)) {
231 // This only estimates the width
232 line_width += m_style.calc_width_for_wrapping(c);
233 }
234 unicode_line += c;
235 }
236
237 // Now make sure that it really fits.
238 std::string::size_type test_cutoff = line_start + end * 2 / 3;
239 while ((end > 0) && (static_cast<uint32_t>(line_start + end) > test_cutoff)) {
240 if (text_width(text.substr(line_start, end), m_style.font->size()) > m_wrapwidth - safety_margin) {
241 --end;
196 } else {242 } else {
197 end_upper = mid - 1;243 break;
198 }244 }
199 }245 }
200246
201 next_line_start = line_end = end_lower;247 // Find last space
248 int32_t last_space = unicode_line.lastIndexOf(0x0020); // space character
249 if ((last_space > 0) && (static_cast<uint32_t>(last_space) > minimum_chars)) {
250 end = last_space;
251 }
252
253 // Make sure that diacritics stay with their base letters, and that
254 // start/end line rules are being followed.
255 while ((end > 0) && (static_cast<uint32_t>(end) > minimum_chars) &&
256 (i18n::cannot_start_line(unicode_line.charAt(end)) ||
257 i18n::cannot_end_line(unicode_line.charAt(end - 1)))) {
258 --end;
259 }
260 assert(end > 0);
261
262 next_line_start = line_end =
263 (i18n::icustring2string(unicode_word.tempSubString(0, end)).size() + line_start);
264}
265
266
267// Returns true if the text won't fit into the alotted width.
268bool WordWrap::line_fits(const std::string& text, uint32_t safety_margin) const {
269 // calc_width_for_wrapping is fast, but it will underestimate the width.
270 // So, we test again with text_width to make sure that the line really fits.
271 return m_style.calc_width_for_wrapping(i18n::make_ligatures(text.c_str()))
272 <= m_wrapwidth - safety_margin &&
273 text_width(text, m_style.font->size()) <= m_wrapwidth - safety_margin;
202}274}
203275
204276
@@ -212,7 +284,7 @@
212 uint32_t calculated_width = 0;284 uint32_t calculated_width = 0;
213285
214 for (uint32_t line = 0; line < m_lines.size(); ++line) {286 for (uint32_t line = 0; line < m_lines.size(); ++line) {
215 uint32_t linewidth = m_style.calc_bare_width(m_lines[line].text);287 uint32_t linewidth = text_width(m_lines[line].text, m_style.font->size());
216 if (linewidth > calculated_width)288 if (linewidth > calculated_width)
217 calculated_width = linewidth;289 calculated_width = linewidth;
218 }290 }
@@ -225,10 +297,12 @@
225 */297 */
226uint32_t WordWrap::height() const298uint32_t WordWrap::height() const
227{299{
228 uint16_t fontheight = m_style.font->height();300 uint16_t fontheight = 0;
229 uint32_t lineskip = m_style.font->lineskip();301 if (!m_lines.empty()) {
302 fontheight = text_height(m_lines[0].text, m_style.font->size());
303 }
230304
231 return fontheight + (m_lines.size() - 1) * lineskip;305 return fontheight * (m_lines.size()) + 2 * LINE_MARGIN;
232}306}
233307
234/**308/**
@@ -273,8 +347,8 @@
273 */347 */
274void WordWrap::draw(RenderTarget & dst, Point where, Align align, uint32_t caret)348void WordWrap::draw(RenderTarget & dst, Point where, Align align, uint32_t caret)
275{349{
276 uint16_t fontheight = m_style.font->height();350 if (m_lines.empty()) return;
277 uint32_t lineskip = m_style.font->lineskip();351
278 uint32_t caretline, caretpos;352 uint32_t caretline, caretpos;
279353
280 calc_wrapped_pos(caret, caretline, caretpos);354 calc_wrapped_pos(caret, caretline, caretpos);
@@ -289,22 +363,42 @@
289 }363 }
290364
291 ++where.y;365 ++where.y;
292 for (uint32_t line = 0; line < m_lines.size(); ++line, where.y += lineskip) {366
367 Align alignment = mirror_alignment(align);
368
369 uint16_t fontheight = text_height(m_lines[0].text, m_style.font->size());
370 for (uint32_t line = 0; line < m_lines.size(); ++line, where.y += fontheight) {
293 if (where.y >= dst.height() || int32_t(where.y + fontheight) <= 0)371 if (where.y >= dst.height() || int32_t(where.y + fontheight) <= 0)
294 continue;372 continue;
295373
296 // Right-align text for RTL languages374 Point point(where.x, where.y);
297 // TODO(GunChleoc): Arabic: we have a ragged edge here for Arabic,375
298 // just like in richtext.cc - bug in TTF_SizeUTF8?376 if (alignment & Align_Right) {
299 Point drawpos(UI::g_fh1->fontset().is_rtl() ?377 point.x += m_wrapwidth - LINE_MARGIN;
300 where.x + m_wrapwidth378 }
301 - m_style.calc_bare_width(i18n::make_ligatures(m_lines[line].text.c_str())) - 2 :379
302 where.x,380 const Image* entry_text_im =
303 where.y);381 UI::g_fh1->render(mode_ == WordWrap::Mode::kDisplay ?
304382 as_uifont(m_lines[line].text,
305 g_fh->draw_text383 m_style.font->size() - UI::g_fh1->fontset().size_offset(),
306 (dst, m_style, drawpos, m_lines[line].text.c_str(), Align(align & Align_Horizontal),384 m_style.fg) :
307 line == caretline ? caretpos : std::numeric_limits<uint32_t>::max());385 as_editorfont(m_lines[line].text,
386 m_style.font->size() - UI::g_fh1->fontset().size_offset(),
387 m_style.fg));
388 UI::correct_for_align(alignment, entry_text_im->width(), fontheight, &point);
389 dst.blit(point, entry_text_im);
390
391 if (mode_ == WordWrap::Mode::kEditor && m_draw_caret && line == caretline) {
392 std::string line_to_caret = m_lines[line].text.substr(0, caretpos);
393 // TODO(GunChleoc): Arabic: Fix cursor position for BIDI text.
394 int caret_x = text_width(line_to_caret, m_style.font->size());
395
396 const Image* caret_image = g_gr->images().get("pics/caret.png");
397 Point caretpt;
398 caretpt.x = point.x + caret_x - caret_image->width() + LINE_MARGIN;
399 caretpt.y = point.y + (fontheight - caret_image->height()) / 2;
400 dst.blit(caretpt, caret_image);
401 }
308 }402 }
309}403}
310404
311405
=== modified file 'src/graphic/wordwrap.h'
--- src/graphic/wordwrap.h 2014-11-27 12:02:08 +0000
+++ src/graphic/wordwrap.h 2015-12-28 21:49:42 +0000
@@ -33,6 +33,11 @@
33 * Helper struct that provides word wrapping and related functionality.33 * Helper struct that provides word wrapping and related functionality.
34 */34 */
35struct WordWrap {35struct WordWrap {
36 enum class Mode {
37 kDisplay,
38 kEditor
39 };
40
36 WordWrap();41 WordWrap();
37 WordWrap(const TextStyle & style, uint32_t wrapwidth = std::numeric_limits<uint32_t>::max());42 WordWrap(const TextStyle & style, uint32_t wrapwidth = std::numeric_limits<uint32_t>::max());
3843
@@ -41,10 +46,11 @@
4146
42 uint32_t wrapwidth() const;47 uint32_t wrapwidth() const;
4348
44 void wrap(const std::string & text);49 void wrap(const std::string & text, WordWrap::Mode mode = WordWrap::Mode::kDisplay);
4550
46 uint32_t width() const;51 uint32_t width() const;
47 uint32_t height() const;52 uint32_t height() const;
53 void set_draw_caret(bool draw_it) {m_draw_caret = draw_it;}
4854
49 void draw55 void draw
50 (RenderTarget & dst, Point where, Align align = Align_Left,56 (RenderTarget & dst, Point where, Align align = Align_Left,
@@ -60,17 +66,23 @@
60 std::string text;66 std::string text;
6167
62 /// Starting offset of this line within the original un-wrapped text68 /// Starting offset of this line within the original un-wrapped text
63 uint32_t start;69 size_t start;
64 };70 };
6571
66 void compute_end_of_line72 void compute_end_of_line
67 (const std::string & text,73 (const std::string & text,
68 std::string::size_type line_start,74 std::string::size_type line_start,
69 std::string::size_type & line_end,75 std::string::size_type & line_end,
70 std::string::size_type & next_line_start);76 std::string::size_type & next_line_start,
77 uint32_t safety_margin);
78
79 bool line_fits(const std::string& text, uint32_t safety_margin) const;
7180
72 TextStyle m_style;81 TextStyle m_style;
73 uint32_t m_wrapwidth;82 uint32_t m_wrapwidth;
83 bool m_draw_caret;
84
85 WordWrap::Mode mode_;
7486
75 std::vector<LineData> m_lines;87 std::vector<LineData> m_lines;
76};88};
7789
=== modified file 'src/ui_basic/messagebox.cc'
--- src/ui_basic/messagebox.cc 2015-10-04 17:40:40 +0000
+++ src/ui_basic/messagebox.cc 2015-12-28 21:49:42 +0000
@@ -46,10 +46,6 @@
46 d(new WLMessageBoxImpl)46 d(new WLMessageBoxImpl)
47{47{
48 d->type = type;48 d->type = type;
49 d->textarea = new MultilineTextarea
50 (this,
51 5, 5, 30, 30,
52 text.c_str(), align);
5349
54 const int32_t outerwidth = parent ?50 const int32_t outerwidth = parent ?
55 parent->get_inner_w() : g_gr->get_xres();51 parent->get_inner_w() : g_gr->get_xres();
@@ -59,6 +55,11 @@
59 assert(outerheight >= 60);55 assert(outerheight >= 60);
60 const int32_t maxwidth = outerwidth - 80;56 const int32_t maxwidth = outerwidth - 80;
61 const int32_t maxheight = outerheight - 60;57 const int32_t maxheight = outerheight - 60;
58 d->textarea = new MultilineTextarea
59 (this,
60 5, 5, maxwidth, maxheight,
61 text.c_str(), align);
62
62 uint32_t width, height;63 uint32_t width, height;
63 std::string font = d->textarea->get_font_name();64 std::string font = d->textarea->get_font_name();
64 int32_t fontsize = d->textarea->get_font_size();65 int32_t fontsize = d->textarea->get_font_size();
6566
=== modified file 'src/ui_basic/multilineeditbox.cc'
--- src/ui_basic/multilineeditbox.cc 2015-10-09 15:36:22 +0000
+++ src/ui_basic/multilineeditbox.cc 2015-12-28 21:49:42 +0000
@@ -220,6 +220,7 @@
220220
221 do {221 do {
222 --cursor;222 --cursor;
223 // TODO(GunChleoc): When switchover to g_fh1 is complete, see if we can go full ICU here.
223 } while (cursor > 0 && Utf8::is_utf8_extended(text[cursor]));224 } while (cursor > 0 && Utf8::is_utf8_extended(text[cursor]));
224225
225 return cursor;226 return cursor;
@@ -482,6 +483,8 @@
482483
483 d->refresh_ww();484 d->refresh_ww();
484485
486 d->ww.set_draw_caret(has_focus());
487
485 d->ww.draw488 d->ww.draw
486 (dst, Point(0, -int32_t(d->scrollbar.get_scrollpos())), Align_Left,489 (dst, Point(0, -int32_t(d->scrollbar.get_scrollpos())), Align_Left,
487 has_focus() ? d->cursor_pos : std::numeric_limits<uint32_t>::max());490 has_focus() ? d->cursor_pos : std::numeric_limits<uint32_t>::max());
@@ -563,7 +566,7 @@
563 ww.set_style(textstyle);566 ww.set_style(textstyle);
564 ww.set_wrapwidth(owner.get_w() - ms_scrollbar_w);567 ww.set_wrapwidth(owner.get_w() - ms_scrollbar_w);
565568
566 ww.wrap(text);569 ww.wrap(text, WordWrap::Mode::kEditor);
567 ww_valid = true;570 ww_valid = true;
568571
569 int32_t textheight = ww.height();572 int32_t textheight = ww.height();
570573
=== modified file 'src/ui_basic/multilinetextarea.cc'
--- src/ui_basic/multilinetextarea.cc 2014-11-27 16:43:37 +0000
+++ src/ui_basic/multilinetextarea.cc 2015-12-28 21:49:42 +0000
@@ -117,28 +117,36 @@
117{117{
118 uint32_t height;118 uint32_t height;
119119
120 if (m_text.compare(0, 3, "<rt")) {120 // We wrap the text twice. We need to do this to account for the presence/absence of the scollbar.
121 m->isrichtext = false;121 bool scroolbar_was_enabled = m_scrollbar.is_enabled();
122 m->ww.set_wrapwidth(get_eff_w());122 for (int i = 0; i < 2; ++i) {
123 m->ww.wrap(m_text);123 if (m_text.compare(0, 3, "<rt")) {
124 height = m->ww.height();124 m->isrichtext = false;
125 } else {125 m->ww.set_wrapwidth(get_eff_w());
126 m->isrichtext = true;126 m->ww.wrap(m_text);
127 m->rt.set_width(get_eff_w() - 2 * RICHTEXT_MARGIN);127 height = m->ww.height();
128 m->rt.parse(m_text);128 } else {
129 height = m->rt.height() + 2 * RICHTEXT_MARGIN;129 m->isrichtext = true;
130 m->rt.set_width(get_eff_w() - 2 * RICHTEXT_MARGIN);
131 m->rt.parse(m_text);
132 height = m->rt.height() + 2 * RICHTEXT_MARGIN;
133 }
134
135 bool setbottom = false;
136
137 if (m_scrollmode == ScrollLog)
138 if (m_scrollbar.get_scrollpos() >= m_scrollbar.get_steps() - 1)
139 setbottom = true;
140
141 m_scrollbar.set_steps(height - get_h());
142 if (setbottom)
143 m_scrollbar.set_scrollpos(height - get_h());
144
145 if (m_scrollbar.is_enabled() == scroolbar_was_enabled) {
146 break; // No need to wrap twice.
147 }
130 }148 }
131149
132 bool setbottom = false;
133
134 if (m_scrollmode == ScrollLog)
135 if (m_scrollbar.get_scrollpos() >= m_scrollbar.get_steps() - 1)
136 setbottom = true;
137
138 m_scrollbar.set_steps(height - get_h());
139 if (setbottom)
140 m_scrollbar.set_scrollpos(height - get_h());
141
142 update(0, 0, get_eff_w(), get_h());150 update(0, 0, get_eff_w(), get_h());
143}151}
144152
145153
=== modified file 'src/ui_basic/multilinetextarea.h'
--- src/ui_basic/multilinetextarea.h 2014-10-28 12:53:29 +0000
+++ src/ui_basic/multilinetextarea.h 2015-12-28 21:49:42 +0000
@@ -57,7 +57,7 @@
57 void set_font(std::string name, int32_t size, RGBColor fg);57 void set_font(std::string name, int32_t size, RGBColor fg);
5858
59 uint32_t scrollbar_w() const {return 24;}59 uint32_t scrollbar_w() const {return 24;}
60 uint32_t get_eff_w() const {return get_w() - scrollbar_w();}60 uint32_t get_eff_w() const {return m_scrollbar.is_enabled() ? get_w() - scrollbar_w() : get_w();}
6161
62 void set_color(RGBColor fg) {m_fcolor = fg;}62 void set_color(RGBColor fg) {m_fcolor = fg;}
6363

Subscribers

People subscribed via source and target branches

to status/vote changes: