From a8dd8c6f30d89034a3b8523bb5176eef958eed40 Mon Sep 17 00:00:00 2001 From: poire-z Date: Mon, 12 Dec 2016 23:41:16 +0100 Subject: [PATCH] textboxwidget: even better text wrapping util.isSplitable() accepts now also the previous char to help decide if a space can be used to split a line. TextBoxWidget:_splitCharWidthList() : simplified logic --- frontend/ui/widget/textboxwidget.lua | 43 ++++++++++++++-------------- frontend/util.lua | 18 ++++++------ spec/unit/util_spec.lua | 33 +++++++++++++++++++++ 3 files changed, 65 insertions(+), 29 deletions(-) diff --git a/frontend/ui/widget/textboxwidget.lua b/frontend/ui/widget/textboxwidget.lua index 1f4d53ee0..adbdfd9ae 100644 --- a/frontend/ui/widget/textboxwidget.lua +++ b/frontend/ui/widget/textboxwidget.lua @@ -118,31 +118,32 @@ function TextBoxWidget:_splitCharWidthList() cur_line_text = table.concat(self.charlist, "", offset, idx - 1) else -- Backtrack the string until the length fit into one line. + -- We'll give next and prev chars to isSplitable() for a wiser decision local c = self.char_width_list[idx].char - -- We give next char to isSplitable() for a wiser decision - local next_c = idx+1 <= size and self.char_width_list[idx+1].char or nil - if util.isSplitable(c, next_c) then + local next_c = idx+1 <= size and self.char_width_list[idx+1].char or false + local prev_c = idx-1 >= 1 and self.char_width_list[idx-1].char or false + local adjusted_idx = idx + local adjusted_width = cur_line_width + while adjusted_idx > offset and not util.isSplitable(c, next_c, prev_c) do + adjusted_width = adjusted_width - self.char_width_list[adjusted_idx].width + adjusted_idx = adjusted_idx - 1 + next_c = c + c = prev_c + prev_c = adjusted_idx-1 >= 1 and self.char_width_list[adjusted_idx-1].char or false + end + if adjusted_idx == offset or adjusted_idx == idx then + -- either a very long english word ocuppying more than one line, + -- or the excessive char is itself splitable: + -- we let that excessive char for next line cur_line_text = table.concat(self.charlist, "", offset, idx - 1) cur_line_width = cur_line_width - self.char_width_list[idx].width else - local adjusted_idx = idx - local adjusted_width = cur_line_width - repeat - adjusted_width = adjusted_width - self.char_width_list[adjusted_idx].width - if adjusted_idx == 1 then break end - adjusted_idx = adjusted_idx - 1 - next_c = c - c = self.char_width_list[adjusted_idx].char - until adjusted_idx == offset or util.isSplitable(c, next_c) - if adjusted_idx == offset then -- a very long english word ocuppying more than one line - cur_line_text = table.concat(self.charlist, "", offset, idx - 1) - cur_line_width = cur_line_width - self.char_width_list[idx].width - else - cur_line_text = table.concat(self.charlist, "", offset, adjusted_idx) - cur_line_width = adjusted_width - idx = adjusted_idx + 1 - end - end -- endif util.isSplitable(c) + -- we backtracked and we're below max width, we can let the + -- splitable char on this line + cur_line_text = table.concat(self.charlist, "", offset, adjusted_idx) + cur_line_width = adjusted_width + idx = adjusted_idx + 1 + end end -- endif cur_line_width > self.width if cur_line_width < 0 then break end self.vertical_string_list[ln] = { diff --git a/frontend/util.lua b/frontend/util.lua index fd61dd6d0..825fa14a9 100644 --- a/frontend/util.lua +++ b/frontend/util.lua @@ -150,22 +150,24 @@ end -- specific punctuation : e.g. "word :" or "word )" -- (In french, there is a space before a colon, and it better -- not be wrapped there.) --- Includes U+00BB >> (right double angle quotation mark) and --- U+201D '' (right double quotation mark) -local non_splitable_space_tailers = ":;,.!?)]}$%-=/<>»”" +local non_splitable_space_tailers = ":;,.!?)]}$%=-+*/|<>»”" +-- Same if a space has some specific other punctuation before it +local non_splitable_space_leaders = "([{$=-+*/|<>«“" -- Test whether a string could be separated by this char for multi-line rendering --- Optional next char may be provided to help make the decision -function util.isSplitable(c, next_c) +-- Optional next or prev chars may be provided to help make the decision +function util.isSplitable(c, next_c, prev_c) if util.isCJKChar(c) then -- a CJKChar is a word in itself, and so is splitable return true elseif c == " " then -- we only split on a space (so punctuation sticks to prev word) - -- if next_c is provided, we can make a better decision + -- if next_c or prev_c is provided, we can make a better decision if next_c and non_splitable_space_tailers:find(next_c, 1, true) then - -- this space is followed by some punctuation that is better - -- kept with us along previous word + -- this space is followed by some punctuation that is better kept with us + return false + elseif prev_c and non_splitable_space_leaders:find(prev_c, 1, true) then + -- this space is lead by some punctuation that is better kept with us return false else -- we can split on this space diff --git a/spec/unit/util_spec.lua b/spec/unit/util_spec.lua index 557b4905b..e4c573c57 100644 --- a/spec/unit/util_spec.lua +++ b/spec/unit/util_spec.lua @@ -157,5 +157,38 @@ describe("util module", function() }) end) + it("should split text to line with next_c and prev_c - unicode", function() + local text = "Ce test : 1) est « très simple » ; 2 ) simple comme ( 2/2 ) > 50 % ? ok." + local word = "" + local table_of_words = {} + local c + local table_chars = util.splitToChars(text) + for i = 1, #table_chars do + c = table_chars[i] + next_c = i < #table_chars and table_chars[i+1] or nil + prev_c = i > 1 and table_chars[i-1] or nil + word = word .. c + if util.isSplitable(c, next_c, prev_c) then + table.insert(table_of_words, word) + word = "" + end + if i == #table_chars then table.insert(table_of_words, word) end + end + assert.are_same(table_of_words, { + "Ce ", + "test : ", + "1) ", + "est ", + "« très ", + "simple » ; ", + "2 ) ", + "simple ", + "comme ", + "( 2/2 ) > 50 % ? ", + "ok." + }) + end) + + end)