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

Proposed by GunChleoc on 2015-10-09
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 2015-10-09 Approve on 2015-12-25
GunChleoc Resubmit on 2015-12-13
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.
7581. By GunChleoc on 2015-10-09

Removed a NOCOM.

7582. By GunChleoc on 2015-10-09

Removed an obsolete comment.

7583. By GunChleoc on 2015-10-10

Fixed crash in WLMessageBox.

7584. By GunChleoc on 2015-10-12

Merged trunk.

7585. By GunChleoc on 2015-10-19

Merged trunk.

7586. By GunChleoc on 2015-10-23

Merged trunk.

7587. By GunChleoc on 2015-10-23

Fixed overflow.

7588. By GunChleoc on 2015-11-06

Merged trunk.

7589. By GunChleoc on 2015-11-21

Merged trunk.

TiborB (tiborb95) wrote :

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

review: Needs Fixing
7590. By GunChleoc on 2015-11-26

Merged trunk.

7591. By GunChleoc on 2015-12-13

Fixed the width calculations.

7592. By GunChleoc on 2015-12-13

Merged trunk.

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: Resubmit
7593. By GunChleoc on 2015-12-13

Fixed rendering of tooltips etc.

7594. By GunChleoc on 2015-12-13

Fixed center alignment & codecheck.

TiborB (tiborb95) wrote :

It seems to be OK now

review: Approve
7595. By TiborB on 2015-12-28

trunk merged

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/graphic/text/bidi.cc'
2--- src/graphic/text/bidi.cc 2015-10-11 15:00:53 +0000
3+++ src/graphic/text/bidi.cc 2015-12-28 21:49:42 +0000
4@@ -22,10 +22,10 @@
5 #include <map>
6 #include <string>
7
8-#include <unicode/unistr.h>
9 #include <unicode/utypes.h>
10
11 #include "base/log.h"
12+#include "graphic/text/font_set.h"
13
14 namespace {
15 // TODO(GunChleoc): Have a look at the ICU API to see which helper functions can be gained from there.
16@@ -710,6 +710,17 @@
17 return result;
18 }
19
20+// Helper to convert ICU strings to C++ strings
21+std::string icustring2string(const icu::UnicodeString& convertme) {
22+ std::string result;
23+ convertme.toUTF8String(result);
24+ return result;
25+}
26+std::string icuchar2string(const UChar& convertme) {
27+ const icu::UnicodeString temp(convertme);
28+ return icustring2string(temp);
29+}
30+
31 // True if a string contains a character from a CJK code block
32 bool has_cjk_character(const char* input) {
33 bool result = false;
34@@ -745,11 +756,16 @@
35 }
36
37 bool cannot_start_line(const UChar& c) {
38- return kCannottStartLineJapanese.count(c) == 1;
39+ return is_diacritic(c) || is_punctuation_char(c) || kCannottStartLineJapanese.count(c) == 1;
40 }
41
42 bool cannot_end_line(const UChar& c) {
43 return kCannotEndLineJapanese.count(c) == 1;
44 }
45
46+bool is_diacritic(const UChar& c) {
47+ return kArabicDiacritics.count(c) == 1;
48+}
49+
50+
51 } // namespace UI
52
53=== modified file 'src/graphic/text/bidi.h'
54--- src/graphic/text/bidi.h 2015-09-28 18:47:57 +0000
55+++ src/graphic/text/bidi.h 2015-12-28 21:49:42 +0000
56@@ -24,19 +24,21 @@
57 #include <vector>
58
59 #include <unicode/uchar.h>
60-
61-#include "graphic/text/font_set.h"
62+#include <unicode/unistr.h>
63
64 // BiDi support for RTL languages
65 namespace i18n {
66- std::string make_ligatures(const char* input);
67- std::string line2bidi(const char* input);
68- std::vector<std::string> split_cjk_word(const char* input);
69- bool has_rtl_character(const char* input);
70- bool has_rtl_character(std::vector<std::string> input);
71- bool has_cjk_character(const char* input);
72- bool cannot_start_line(const UChar& c);
73- bool cannot_end_line(const UChar& c);
74+std::string make_ligatures(const char* input);
75+std::string line2bidi(const char* input);
76+std::vector<std::string> split_cjk_word(const char* input);
77+bool has_rtl_character(const char* input);
78+bool has_rtl_character(std::vector<std::string> input);
79+std::string icustring2string(const UnicodeString& convertme);
80+std::string icuchar2string(const UChar& convertme);
81+bool has_cjk_character(const char* input);
82+bool cannot_start_line(const UChar& c);
83+bool cannot_end_line(const UChar& c);
84+bool is_diacritic(const UChar& c);
85
86 } // namespace UI
87
88
89=== modified file 'src/graphic/text/rt_parse.cc'
90--- src/graphic/text/rt_parse.cc 2014-09-09 17:15:20 +0000
91+++ src/graphic/text/rt_parse.cc 2015-12-28 21:49:42 +0000
92@@ -25,6 +25,7 @@
93 #include <vector>
94
95 #include <SDL.h>
96+#include <boost/algorithm/string.hpp>
97 #include <boost/format.hpp>
98
99 #include "graphic/text/rt_errors_impl.h"
100@@ -138,8 +139,11 @@
101 size_t line = ts.line(), col = ts.col();
102 std::string text = ts.till_any("<");
103 if (text != "") {
104- if (!tc.text_allowed)
105+ if (!tc.text_allowed) {
106 throw SyntaxErrorImpl(line, col, "no text, as only tags are allowed here", text, ts.peek(100));
107+ }
108+ boost::replace_all(text, "&gt;", ">");
109+ boost::replace_all(text, "&lt;", "<");
110 m_childs.push_back(new Child(text));
111 }
112
113@@ -181,6 +185,7 @@
114 tc.allowed_attrs.insert("padding_b");
115 tc.allowed_attrs.insert("padding_t");
116 tc.allowed_attrs.insert("db_show_spaces");
117+ tc.allowed_attrs.insert("keep_spaces"); // Keeps blank spaces intact for text editing
118 tc.allowed_attrs.insert("background");
119
120 tc.allowed_childs.insert("p");
121
122=== modified file 'src/graphic/text/rt_render.cc'
123--- src/graphic/text/rt_render.cc 2015-09-28 18:47:57 +0000
124+++ src/graphic/text/rt_render.cc 2015-12-28 21:49:42 +0000
125@@ -400,7 +400,7 @@
126 Texture* render(TextureCache* texture_cache) override {
127 if (m_show_spaces) {
128 Texture* rv = new Texture(m_w, m_h);
129- fill_rect(Rect(0, 0, m_w, m_h), RGBAColor(0xff, 0, 0, 0xff), rv);
130+ fill_rect(Rect(0, 0, m_w, m_h), RGBAColor(0xcc, 0, 0, 0xcc), rv);
131 return rv;
132 }
133 return TextNode::render(texture_cache);
134@@ -711,17 +711,26 @@
135 if (i18n::has_rtl_character(txt.c_str())) {
136 std::string previous_word;
137 std::vector<RenderNode*>::iterator it = text_nodes.begin();
138+ std::vector<WordSpacerNode*> spacer_nodes;
139
140 // Collect the word nodes
141 while (ts.pos() < txt.size()) {
142+ std::size_t cpos = ts.pos();
143 ts.skip_ws();
144+ spacer_nodes.clear();
145+
146+ // We only know if the spacer goes to the left or right after having a look at the current word.
147+ for (uint16_t ws_indx = 0; ws_indx < ts.pos() - cpos; ws_indx++) {
148+ spacer_nodes.push_back(new WordSpacerNode(font_cache_.get_font(&ns), ns));
149+ }
150+
151 word = ts.till_any_or_end(" \t\n\r");
152 if (!word.empty()) {
153 bool word_is_bidi = i18n::has_rtl_character(word.c_str());
154 word = i18n::make_ligatures(word.c_str());
155 if (word_is_bidi || i18n::has_rtl_character(previous_word.c_str())) {
156- if (!previous_word.empty()) {
157- text_nodes.insert(text_nodes.begin(), new WordSpacerNode(font_cache_.get_font(&ns), ns));
158+ for (WordSpacerNode* spacer: spacer_nodes) {
159+ it = text_nodes.insert(text_nodes.begin(), spacer);
160 }
161 if (word_is_bidi) {
162 word = i18n::line2bidi(word.c_str());
163@@ -732,8 +741,11 @@
164 if (it < text_nodes.end()) {
165 ++it;
166 }
167- if (!previous_word.empty()) {
168- it = text_nodes.insert(it, new WordSpacerNode(font_cache_.get_font(&ns), ns));
169+ for (WordSpacerNode* spacer: spacer_nodes) {
170+ it = text_nodes.insert(it, spacer);
171+ if (it < text_nodes.end()) {
172+ ++it;
173+ }
174 }
175 it = text_nodes.insert(it, new TextNode(font_cache_.get_font(&ns), ns, word));
176 }
177@@ -749,7 +761,7 @@
178 while (ts.pos() < txt.size()) {
179 std::size_t cpos = ts.pos();
180 ts.skip_ws();
181- if (ts.pos() != cpos) {
182+ for (uint16_t ws_indx = 0; ws_indx < ts.pos() - cpos; ws_indx++) {
183 nodes.push_back(new WordSpacerNode(font_cache_.get_font(&ns), ns));
184 }
185 word = ts.till_any_or_end(" \t\n\r");
186@@ -1072,8 +1084,9 @@
187 else if (align == "center" || align == "middle") m_rn->set_valign(UI::Align::Align_Center);
188 }
189 }
190-private:
191+protected:
192 bool shrink_to_fit_;
193+private:
194 uint16_t m_w;
195 SubTagRenderNode* m_rn;
196 };
197@@ -1089,6 +1102,7 @@
198 void handle_unique_attributes() override {
199 const AttrMap& a = m_tag.attrs();
200 WordSpacerNode::show_spaces(a.has("db_show_spaces") ? a["db_show_spaces"].get_bool() : 0);
201+ shrink_to_fit_ = shrink_to_fit_ && (a.has("keep_spaces") ? !a["keep_spaces"].get_bool() : true);
202 }
203 };
204
205
206=== modified file 'src/graphic/text_layout.cc'
207--- src/graphic/text_layout.cc 2015-09-24 18:45:27 +0000
208+++ src/graphic/text_layout.cc 2015-12-28 21:49:42 +0000
209@@ -22,6 +22,7 @@
210 #include <map>
211
212 #include <SDL_ttf.h>
213+#include <boost/algorithm/string.hpp>
214 #include <boost/format.hpp>
215
216 #include "base/utf8.h"
217@@ -30,6 +31,13 @@
218 #include "graphic/text/font_set.h"
219 #include "graphic/text_constants.h"
220
221+std::string richtext_escape(const std::string& given_text) {
222+ std::string text = given_text;
223+ boost::replace_all(text, ">", "&gt;");
224+ boost::replace_all(text, "<", "&lt;");
225+ return text;
226+}
227+
228 std::string as_game_tip(const std::string& txt) {
229 static boost::format f
230 ("<rt padding_l=48 padding_t=28 padding_r=48 padding_b=28>"
231@@ -94,6 +102,31 @@
232 }
233
234 /**
235+ * Get a width estimate for text wrapping.
236+ */
237+uint32_t TextStyle::calc_width_for_wrapping(const UChar& c) const {
238+ int result = 0;
239+ TTF_GlyphMetrics(font->get_ttf_font(), c, nullptr, nullptr, nullptr, nullptr, &result);
240+ return result;
241+}
242+
243+/**
244+ * Get a width estimate for text wrapping.
245+ */
246+uint32_t TextStyle::calc_width_for_wrapping(const std::string & text) const
247+{
248+ int result = 0;
249+ const icu::UnicodeString parseme(text.c_str());
250+ for (int i = 0; i < parseme.length(); ++i) {
251+ UChar c = parseme.charAt(i);
252+ if (!i18n::is_diacritic(c)) {
253+ result += calc_width_for_wrapping(c);
254+ }
255+ }
256+ return result;
257+}
258+
259+/**
260 * Compute the bare width (without caret padding) of the given string.
261 */
262 uint32_t TextStyle::calc_bare_width(const std::string & text) const
263
264=== modified file 'src/graphic/text_layout.h'
265--- src/graphic/text_layout.h 2014-12-06 12:22:35 +0000
266+++ src/graphic/text_layout.h 2015-12-28 21:49:42 +0000
267@@ -21,6 +21,7 @@
268 #define WL_GRAPHIC_TEXT_LAYOUT_H
269
270 #include <string>
271+#include <unicode/uchar.h>
272
273 #include "graphic/font.h"
274 #include "graphic/color.h"
275@@ -34,6 +35,11 @@
276 }
277
278 /**
279+ * Escapes reserved characters for Richtext.
280+ */
281+std::string richtext_escape(const std::string& given_text);
282+
283+/**
284 * Convenience functions to convert simple text into a valid block
285 * of rich text which can be rendered.
286 */
287@@ -71,6 +77,8 @@
288 static const TextStyle & ui_big();
289 static const TextStyle & ui_small();
290 uint32_t calc_bare_width(const std::string & text) const;
291+ uint32_t calc_width_for_wrapping(const UChar& c) const;
292+ uint32_t calc_width_for_wrapping(const std::string & text) const;
293 void calc_bare_height_heuristic(const std::string & text, int32_t & miny, int32_t & maxy) const;
294 void setup() const;
295
296
297=== modified file 'src/graphic/wordwrap.cc'
298--- src/graphic/wordwrap.cc 2015-09-28 06:41:58 +0000
299+++ src/graphic/wordwrap.cc 2015-12-28 21:49:42 +0000
300@@ -23,12 +23,42 @@
301
302 #include "graphic/wordwrap.h"
303
304+#include <boost/format.hpp>
305+#include <unicode/uchar.h>
306+#include <unicode/unistr.h>
307+
308 #include "base/log.h"
309-#include "graphic/font_handler.h"
310 #include "graphic/font_handler1.h"
311+#include "graphic/graphic.h"
312 #include "graphic/rendertarget.h"
313 #include "graphic/text/bidi.h"
314
315+namespace {
316+std::string as_editorfont(const std::string& text,
317+ int ptsize = UI_FONT_SIZE_SMALL,
318+ const RGBColor& clr = UI_FONT_CLR_FG) {
319+ // UI Text is always bold due to historic reasons
320+ static boost::format
321+ f("<rt keep_spaces=1><p><font face=serif size=%i bold=1 shadow=1 color=%s>%s</font></p></rt>");
322+ f % ptsize;
323+ f % clr.hex_value();
324+ f % richtext_escape(text);
325+ return f.str();
326+}
327+
328+// This is inefficient; only call when we need the exact width.
329+uint32_t text_width(const std::string& text, int ptsize) {
330+ return UI::g_fh1->render(as_editorfont(text, ptsize - UI::g_fh1->fontset().size_offset()))->width();
331+}
332+
333+// This is inefficient; only call when we need the exact height.
334+uint32_t text_height(const std::string& text, int ptsize) {
335+ return UI::g_fh1->render(as_editorfont(text.empty() ? "." : text,
336+ ptsize - UI::g_fh1->fontset().size_offset()))->height();
337+}
338+
339+} // namespace
340+
341 namespace UI {
342
343 /**
344@@ -36,12 +66,12 @@
345 * and a default-constructed text style.
346 */
347 WordWrap::WordWrap() :
348- m_wrapwidth(std::numeric_limits<uint32_t>::max())
349+ m_wrapwidth(std::numeric_limits<uint32_t>::max()), m_draw_caret(false), mode_(WordWrap::Mode::kDisplay)
350 {
351 }
352
353 WordWrap::WordWrap(const TextStyle & style, uint32_t gwrapwidth) :
354- m_style(style)
355+ m_style(style), m_draw_caret(false), mode_(WordWrap::Mode::kDisplay)
356 {
357 m_wrapwidth = gwrapwidth;
358
359@@ -82,20 +112,19 @@
360 * Perform the wrapping computations for the given text and fill in
361 * the private data containing the wrapped results.
362 */
363-void WordWrap::wrap(const std::string & text)
364+void WordWrap::wrap(const std::string & text, WordWrap::Mode mode)
365 {
366- //static int count = 0;
367- //log("word_wrap_text(%u): %i\n", m_wrapwidth, ++count);
368-
369+ mode_ = mode;
370 m_lines.clear();
371
372 std::string::size_type line_start = 0;
373+ uint32_t margin = m_style.calc_width_for_wrapping(0x2003); // Em space
374
375 while (line_start <= text.size()) {
376 std::string::size_type next_line_start;
377 std::string::size_type line_end;
378
379- compute_end_of_line(text, line_start, line_end, next_line_start);
380+ compute_end_of_line(text, line_start, line_end, next_line_start, margin);
381
382 LineData ld;
383 ld.start = line_start;
384@@ -115,15 +144,19 @@
385 (const std::string & text,
386 std::string::size_type line_start,
387 std::string::size_type & line_end,
388- std::string::size_type & next_line_start)
389+ std::string::size_type & next_line_start,
390+ uint32_t safety_margin)
391 {
392+ std::string::size_type minimum_chars = 1; // So we won't get empty lines
393+ assert(m_wrapwidth > safety_margin);
394+
395 std::string::size_type orig_end = text.find('\n', line_start);
396 if (orig_end == std::string::npos)
397 orig_end = text.size();
398
399- if (m_wrapwidth == std::numeric_limits<uint32_t>::max() || orig_end - line_start <= 1) {
400+ if (m_wrapwidth == std::numeric_limits<uint32_t>::max() || orig_end - line_start <= minimum_chars) {
401 // Special fast path when wrapping is disabled or
402- // original text line contains at most one character
403+ // original text line contains at most minimum_chars characters
404 line_end = orig_end;
405 next_line_start = orig_end + 1;
406 return;
407@@ -131,9 +164,8 @@
408
409
410 // Optimism: perhaps the entire line fits?
411- // TODO(GunChleoc): Arabic: Multiple calls of make_ligatures are inefficient.
412- if (m_style.calc_bare_width(i18n::make_ligatures(text.substr(line_start, orig_end - line_start).c_str()))
413- <= m_wrapwidth) {
414+ if (text_width(text.substr(line_start, orig_end - line_start), m_style.font->size())
415+ <= m_wrapwidth - safety_margin) {
416 line_end = orig_end;
417 next_line_start = orig_end + 1;
418 return;
419@@ -150,8 +182,7 @@
420 while (end_upper - end_lower > 4) {
421 std::string::size_type mid = end_lower + (end_upper - end_lower + 1) / 2;
422
423- if (m_style.calc_bare_width(i18n::make_ligatures(text.substr(line_start, mid - line_start).c_str()))
424- <= m_wrapwidth) {
425+ if (line_fits(i18n::make_ligatures(text.substr(line_start, mid - line_start).c_str()), safety_margin)) {
426 end_lower = mid;
427 } else {
428 end_upper = mid - 1;
429@@ -171,11 +202,10 @@
430 break; // we already know that this cannot possibly fit
431
432 // check whether the next word still fits
433- if (m_style.calc_bare_width(
434- i18n::make_ligatures(text.substr(line_start, nextspace - line_start).c_str()))
435- > m_wrapwidth)
436+ if (!line_fits(i18n::make_ligatures(text.substr(line_start, nextspace - line_start).c_str()),
437+ safety_margin)) {
438 break;
439-
440+ }
441 space = nextspace;
442 }
443
444@@ -185,20 +215,62 @@
445 return;
446 }
447
448- // Nasty special case: the line starts with a single word that is too big to fit
449- // Continue the binary search until we narrowed down exactly how many characters fit
450- while (end_upper > end_lower) {
451- std::string::size_type mid = end_lower + (end_upper - end_lower + 1) / 2;
452-
453- if (m_style.calc_bare_width(i18n::make_ligatures(text.substr(line_start, mid - line_start).c_str()))
454- <= m_wrapwidth) {
455- end_lower = mid;
456+
457+ // The line didn't fit.
458+ // We just do a linear search ahead until we hit the max.
459+ const icu::UnicodeString unicode_word(text.substr(line_start, orig_end).c_str());
460+ uint32_t line_width = 0;
461+ int32_t end = -1;
462+ icu::UnicodeString unicode_line;
463+
464+ while ((line_width < (m_wrapwidth - safety_margin)) && (end < unicode_word.length())) {
465+ ++end;
466+ UChar c = unicode_word.charAt(end);
467+ // Diacritics do not add to the line width
468+ if (!i18n::is_diacritic(c)) {
469+ // This only estimates the width
470+ line_width += m_style.calc_width_for_wrapping(c);
471+ }
472+ unicode_line += c;
473+ }
474+
475+ // Now make sure that it really fits.
476+ std::string::size_type test_cutoff = line_start + end * 2 / 3;
477+ while ((end > 0) && (static_cast<uint32_t>(line_start + end) > test_cutoff)) {
478+ if (text_width(text.substr(line_start, end), m_style.font->size()) > m_wrapwidth - safety_margin) {
479+ --end;
480 } else {
481- end_upper = mid - 1;
482+ break;
483 }
484 }
485
486- next_line_start = line_end = end_lower;
487+ // Find last space
488+ int32_t last_space = unicode_line.lastIndexOf(0x0020); // space character
489+ if ((last_space > 0) && (static_cast<uint32_t>(last_space) > minimum_chars)) {
490+ end = last_space;
491+ }
492+
493+ // Make sure that diacritics stay with their base letters, and that
494+ // start/end line rules are being followed.
495+ while ((end > 0) && (static_cast<uint32_t>(end) > minimum_chars) &&
496+ (i18n::cannot_start_line(unicode_line.charAt(end)) ||
497+ i18n::cannot_end_line(unicode_line.charAt(end - 1)))) {
498+ --end;
499+ }
500+ assert(end > 0);
501+
502+ next_line_start = line_end =
503+ (i18n::icustring2string(unicode_word.tempSubString(0, end)).size() + line_start);
504+}
505+
506+
507+// Returns true if the text won't fit into the alotted width.
508+bool WordWrap::line_fits(const std::string& text, uint32_t safety_margin) const {
509+ // calc_width_for_wrapping is fast, but it will underestimate the width.
510+ // So, we test again with text_width to make sure that the line really fits.
511+ return m_style.calc_width_for_wrapping(i18n::make_ligatures(text.c_str()))
512+ <= m_wrapwidth - safety_margin &&
513+ text_width(text, m_style.font->size()) <= m_wrapwidth - safety_margin;
514 }
515
516
517@@ -212,7 +284,7 @@
518 uint32_t calculated_width = 0;
519
520 for (uint32_t line = 0; line < m_lines.size(); ++line) {
521- uint32_t linewidth = m_style.calc_bare_width(m_lines[line].text);
522+ uint32_t linewidth = text_width(m_lines[line].text, m_style.font->size());
523 if (linewidth > calculated_width)
524 calculated_width = linewidth;
525 }
526@@ -225,10 +297,12 @@
527 */
528 uint32_t WordWrap::height() const
529 {
530- uint16_t fontheight = m_style.font->height();
531- uint32_t lineskip = m_style.font->lineskip();
532+ uint16_t fontheight = 0;
533+ if (!m_lines.empty()) {
534+ fontheight = text_height(m_lines[0].text, m_style.font->size());
535+ }
536
537- return fontheight + (m_lines.size() - 1) * lineskip;
538+ return fontheight * (m_lines.size()) + 2 * LINE_MARGIN;
539 }
540
541 /**
542@@ -273,8 +347,8 @@
543 */
544 void WordWrap::draw(RenderTarget & dst, Point where, Align align, uint32_t caret)
545 {
546- uint16_t fontheight = m_style.font->height();
547- uint32_t lineskip = m_style.font->lineskip();
548+ if (m_lines.empty()) return;
549+
550 uint32_t caretline, caretpos;
551
552 calc_wrapped_pos(caret, caretline, caretpos);
553@@ -289,22 +363,42 @@
554 }
555
556 ++where.y;
557- for (uint32_t line = 0; line < m_lines.size(); ++line, where.y += lineskip) {
558+
559+ Align alignment = mirror_alignment(align);
560+
561+ uint16_t fontheight = text_height(m_lines[0].text, m_style.font->size());
562+ for (uint32_t line = 0; line < m_lines.size(); ++line, where.y += fontheight) {
563 if (where.y >= dst.height() || int32_t(where.y + fontheight) <= 0)
564 continue;
565
566- // Right-align text for RTL languages
567- // TODO(GunChleoc): Arabic: we have a ragged edge here for Arabic,
568- // just like in richtext.cc - bug in TTF_SizeUTF8?
569- Point drawpos(UI::g_fh1->fontset().is_rtl() ?
570- where.x + m_wrapwidth
571- - m_style.calc_bare_width(i18n::make_ligatures(m_lines[line].text.c_str())) - 2 :
572- where.x,
573- where.y);
574-
575- g_fh->draw_text
576- (dst, m_style, drawpos, m_lines[line].text.c_str(), Align(align & Align_Horizontal),
577- line == caretline ? caretpos : std::numeric_limits<uint32_t>::max());
578+ Point point(where.x, where.y);
579+
580+ if (alignment & Align_Right) {
581+ point.x += m_wrapwidth - LINE_MARGIN;
582+ }
583+
584+ const Image* entry_text_im =
585+ UI::g_fh1->render(mode_ == WordWrap::Mode::kDisplay ?
586+ as_uifont(m_lines[line].text,
587+ m_style.font->size() - UI::g_fh1->fontset().size_offset(),
588+ m_style.fg) :
589+ as_editorfont(m_lines[line].text,
590+ m_style.font->size() - UI::g_fh1->fontset().size_offset(),
591+ m_style.fg));
592+ UI::correct_for_align(alignment, entry_text_im->width(), fontheight, &point);
593+ dst.blit(point, entry_text_im);
594+
595+ if (mode_ == WordWrap::Mode::kEditor && m_draw_caret && line == caretline) {
596+ std::string line_to_caret = m_lines[line].text.substr(0, caretpos);
597+ // TODO(GunChleoc): Arabic: Fix cursor position for BIDI text.
598+ int caret_x = text_width(line_to_caret, m_style.font->size());
599+
600+ const Image* caret_image = g_gr->images().get("pics/caret.png");
601+ Point caretpt;
602+ caretpt.x = point.x + caret_x - caret_image->width() + LINE_MARGIN;
603+ caretpt.y = point.y + (fontheight - caret_image->height()) / 2;
604+ dst.blit(caretpt, caret_image);
605+ }
606 }
607 }
608
609
610=== modified file 'src/graphic/wordwrap.h'
611--- src/graphic/wordwrap.h 2014-11-27 12:02:08 +0000
612+++ src/graphic/wordwrap.h 2015-12-28 21:49:42 +0000
613@@ -33,6 +33,11 @@
614 * Helper struct that provides word wrapping and related functionality.
615 */
616 struct WordWrap {
617+ enum class Mode {
618+ kDisplay,
619+ kEditor
620+ };
621+
622 WordWrap();
623 WordWrap(const TextStyle & style, uint32_t wrapwidth = std::numeric_limits<uint32_t>::max());
624
625@@ -41,10 +46,11 @@
626
627 uint32_t wrapwidth() const;
628
629- void wrap(const std::string & text);
630+ void wrap(const std::string & text, WordWrap::Mode mode = WordWrap::Mode::kDisplay);
631
632 uint32_t width() const;
633 uint32_t height() const;
634+ void set_draw_caret(bool draw_it) {m_draw_caret = draw_it;}
635
636 void draw
637 (RenderTarget & dst, Point where, Align align = Align_Left,
638@@ -60,17 +66,23 @@
639 std::string text;
640
641 /// Starting offset of this line within the original un-wrapped text
642- uint32_t start;
643+ size_t start;
644 };
645
646 void compute_end_of_line
647 (const std::string & text,
648 std::string::size_type line_start,
649 std::string::size_type & line_end,
650- std::string::size_type & next_line_start);
651+ std::string::size_type & next_line_start,
652+ uint32_t safety_margin);
653+
654+ bool line_fits(const std::string& text, uint32_t safety_margin) const;
655
656 TextStyle m_style;
657 uint32_t m_wrapwidth;
658+ bool m_draw_caret;
659+
660+ WordWrap::Mode mode_;
661
662 std::vector<LineData> m_lines;
663 };
664
665=== modified file 'src/ui_basic/messagebox.cc'
666--- src/ui_basic/messagebox.cc 2015-10-04 17:40:40 +0000
667+++ src/ui_basic/messagebox.cc 2015-12-28 21:49:42 +0000
668@@ -46,10 +46,6 @@
669 d(new WLMessageBoxImpl)
670 {
671 d->type = type;
672- d->textarea = new MultilineTextarea
673- (this,
674- 5, 5, 30, 30,
675- text.c_str(), align);
676
677 const int32_t outerwidth = parent ?
678 parent->get_inner_w() : g_gr->get_xres();
679@@ -59,6 +55,11 @@
680 assert(outerheight >= 60);
681 const int32_t maxwidth = outerwidth - 80;
682 const int32_t maxheight = outerheight - 60;
683+ d->textarea = new MultilineTextarea
684+ (this,
685+ 5, 5, maxwidth, maxheight,
686+ text.c_str(), align);
687+
688 uint32_t width, height;
689 std::string font = d->textarea->get_font_name();
690 int32_t fontsize = d->textarea->get_font_size();
691
692=== modified file 'src/ui_basic/multilineeditbox.cc'
693--- src/ui_basic/multilineeditbox.cc 2015-10-09 15:36:22 +0000
694+++ src/ui_basic/multilineeditbox.cc 2015-12-28 21:49:42 +0000
695@@ -220,6 +220,7 @@
696
697 do {
698 --cursor;
699+ // TODO(GunChleoc): When switchover to g_fh1 is complete, see if we can go full ICU here.
700 } while (cursor > 0 && Utf8::is_utf8_extended(text[cursor]));
701
702 return cursor;
703@@ -482,6 +483,8 @@
704
705 d->refresh_ww();
706
707+ d->ww.set_draw_caret(has_focus());
708+
709 d->ww.draw
710 (dst, Point(0, -int32_t(d->scrollbar.get_scrollpos())), Align_Left,
711 has_focus() ? d->cursor_pos : std::numeric_limits<uint32_t>::max());
712@@ -563,7 +566,7 @@
713 ww.set_style(textstyle);
714 ww.set_wrapwidth(owner.get_w() - ms_scrollbar_w);
715
716- ww.wrap(text);
717+ ww.wrap(text, WordWrap::Mode::kEditor);
718 ww_valid = true;
719
720 int32_t textheight = ww.height();
721
722=== modified file 'src/ui_basic/multilinetextarea.cc'
723--- src/ui_basic/multilinetextarea.cc 2014-11-27 16:43:37 +0000
724+++ src/ui_basic/multilinetextarea.cc 2015-12-28 21:49:42 +0000
725@@ -117,28 +117,36 @@
726 {
727 uint32_t height;
728
729- if (m_text.compare(0, 3, "<rt")) {
730- m->isrichtext = false;
731- m->ww.set_wrapwidth(get_eff_w());
732- m->ww.wrap(m_text);
733- height = m->ww.height();
734- } else {
735- m->isrichtext = true;
736- m->rt.set_width(get_eff_w() - 2 * RICHTEXT_MARGIN);
737- m->rt.parse(m_text);
738- height = m->rt.height() + 2 * RICHTEXT_MARGIN;
739+ // We wrap the text twice. We need to do this to account for the presence/absence of the scollbar.
740+ bool scroolbar_was_enabled = m_scrollbar.is_enabled();
741+ for (int i = 0; i < 2; ++i) {
742+ if (m_text.compare(0, 3, "<rt")) {
743+ m->isrichtext = false;
744+ m->ww.set_wrapwidth(get_eff_w());
745+ m->ww.wrap(m_text);
746+ height = m->ww.height();
747+ } else {
748+ m->isrichtext = true;
749+ m->rt.set_width(get_eff_w() - 2 * RICHTEXT_MARGIN);
750+ m->rt.parse(m_text);
751+ height = m->rt.height() + 2 * RICHTEXT_MARGIN;
752+ }
753+
754+ bool setbottom = false;
755+
756+ if (m_scrollmode == ScrollLog)
757+ if (m_scrollbar.get_scrollpos() >= m_scrollbar.get_steps() - 1)
758+ setbottom = true;
759+
760+ m_scrollbar.set_steps(height - get_h());
761+ if (setbottom)
762+ m_scrollbar.set_scrollpos(height - get_h());
763+
764+ if (m_scrollbar.is_enabled() == scroolbar_was_enabled) {
765+ break; // No need to wrap twice.
766+ }
767 }
768
769- bool setbottom = false;
770-
771- if (m_scrollmode == ScrollLog)
772- if (m_scrollbar.get_scrollpos() >= m_scrollbar.get_steps() - 1)
773- setbottom = true;
774-
775- m_scrollbar.set_steps(height - get_h());
776- if (setbottom)
777- m_scrollbar.set_scrollpos(height - get_h());
778-
779 update(0, 0, get_eff_w(), get_h());
780 }
781
782
783=== modified file 'src/ui_basic/multilinetextarea.h'
784--- src/ui_basic/multilinetextarea.h 2014-10-28 12:53:29 +0000
785+++ src/ui_basic/multilinetextarea.h 2015-12-28 21:49:42 +0000
786@@ -57,7 +57,7 @@
787 void set_font(std::string name, int32_t size, RGBColor fg);
788
789 uint32_t scrollbar_w() const {return 24;}
790- uint32_t get_eff_w() const {return get_w() - scrollbar_w();}
791+ uint32_t get_eff_w() const {return m_scrollbar.is_enabled() ? get_w() - scrollbar_w() : get_w();}
792
793 void set_color(RGBColor fg) {m_fcolor = fg;}
794

Subscribers

People subscribed via source and target branches

to status/vote changes: