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
pull/2427/head
poire-z 7 years ago committed by Qingping Hou
parent fe56ecd301
commit a8dd8c6f30

@ -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] = {

@ -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

@ -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)

Loading…
Cancel
Save