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) cur_line_text = table.concat(self.charlist, "", offset, idx - 1)
else else
-- Backtrack the string until the length fit into one line. -- 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 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 false
local next_c = idx+1 <= size and self.char_width_list[idx+1].char or nil local prev_c = idx-1 >= 1 and self.char_width_list[idx-1].char or false
if util.isSplitable(c, next_c) then 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_text = table.concat(self.charlist, "", offset, idx - 1)
cur_line_width = cur_line_width - self.char_width_list[idx].width cur_line_width = cur_line_width - self.char_width_list[idx].width
else else
local adjusted_idx = idx -- we backtracked and we're below max width, we can let the
local adjusted_width = cur_line_width -- splitable char on this line
repeat cur_line_text = table.concat(self.charlist, "", offset, adjusted_idx)
adjusted_width = adjusted_width - self.char_width_list[adjusted_idx].width cur_line_width = adjusted_width
if adjusted_idx == 1 then break end idx = adjusted_idx + 1
adjusted_idx = adjusted_idx - 1 end
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)
end -- endif cur_line_width > self.width end -- endif cur_line_width > self.width
if cur_line_width < 0 then break end if cur_line_width < 0 then break end
self.vertical_string_list[ln] = { self.vertical_string_list[ln] = {

@ -150,22 +150,24 @@ end
-- specific punctuation : e.g. "word :" or "word )" -- specific punctuation : e.g. "word :" or "word )"
-- (In french, there is a space before a colon, and it better -- (In french, there is a space before a colon, and it better
-- not be wrapped there.) -- not be wrapped there.)
-- Includes U+00BB >> (right double angle quotation mark) and local non_splitable_space_tailers = ":;,.!?)]}$%=-+*/|<>»”"
-- U+201D '' (right double quotation mark) -- Same if a space has some specific other punctuation before it
local non_splitable_space_tailers = ":;,.!?)]}$%-=/<>»”" local non_splitable_space_leaders = "([{$=-+*/|<>«“"
-- Test whether a string could be separated by this char for multi-line rendering -- 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 -- Optional next or prev chars may be provided to help make the decision
function util.isSplitable(c, next_c) function util.isSplitable(c, next_c, prev_c)
if util.isCJKChar(c) then if util.isCJKChar(c) then
-- a CJKChar is a word in itself, and so is splitable -- a CJKChar is a word in itself, and so is splitable
return true return true
elseif c == " " then elseif c == " " then
-- we only split on a space (so punctuation sticks to prev word) -- 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 if next_c and non_splitable_space_tailers:find(next_c, 1, true) then
-- this space is followed by some punctuation that is better -- this space is followed by some punctuation that is better kept with us
-- kept with us along previous word 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 return false
else else
-- we can split on this space -- we can split on this space

@ -157,5 +157,38 @@ describe("util module", function()
}) })
end) 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) end)

Loading…
Cancel
Save