2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/frontend/ui/widget/textboxwidget.lua
Hans-Werner Hilse 3066c86e38 Refactoring hardware abstraction
This is a major overhaul of the hardware abstraction layer.
A few notes:

General platform distinction happens in
  frontend/device.lua
which will delegate everything else to
  frontend/device/<platform_name>/device.lua
which should extend
  frontend/device/generic/device.lua

Screen handling is implemented in
  frontend/device/screen.lua
which includes the *functionality* to support device specifics.
Actually setting up the device specific functionality, however,
is done in the device specific setup code in the relevant
device.lua file.

The same goes for input handling.
2014-11-02 21:19:04 +01:00

277 lines
8.2 KiB
Lua

local Blitbuffer = require("ffi/blitbuffer")
local Widget = require("ui/widget/widget")
local RenderText = require("ui/rendertext")
local Screen = require("device").screen
local Geom = require("ui/geometry")
-- TODO: rename string:gsplit definition
--[[
A TextWidget that handles long text wrapping
--]]
local TextBoxWidget = Widget:new{
text = nil,
face = nil,
bold = nil,
fgcolor = Blitbuffer.COLOR_BLACK,
width = 400, -- in pixels
height = nil,
first_line = 1,
virtual_line = 1, -- used by scroll bar
line_height = 0.3, -- in em
v_list = nil,
_bb = nil,
_length = 0,
}
function TextBoxWidget:init()
local v_list = nil
if self.height then
v_list = self:_getCurrentVerticalList()
else
v_list = self:_getVerticalList()
end
self:_render(v_list)
end
function TextBoxWidget:_wrapGreedyAlg(h_list)
local cur_line_width = 0
local cur_line = {}
local v_list = {}
for k,w in ipairs(h_list) do
cur_line_width = cur_line_width + w.width
if w.word == "\n" then
if cur_line_width > 0 then
-- hard line break
table.insert(v_list, cur_line)
cur_line = {}
cur_line_width = 0
end
elseif cur_line_width > self.width then
-- wrap to next line
table.insert(v_list, cur_line)
cur_line = {}
cur_line_width = w.width
table.insert(cur_line, w)
else
table.insert(cur_line, w)
end
end
-- handle last line
table.insert(v_list, cur_line)
return v_list
end
--[[
Lua doesn't have a string.split() function and most of the time
you don't really need it because string.gmatch() is enough.
However string.gmatch() has one significant disadvantage for me:
You can't split a string while matching both the delimited
strings and the delimiters themselves without tracking positions
and substrings. The string.gsplit() function below takes care of
this problem.
Author: Peter Odding
License: MIT/X11
Source: http://snippets.luacode.org/snippets/String_splitting_130
--]]
function string:gsplit(pattern, capture)
pattern = pattern and tostring(pattern) or '%s+'
if (''):find(pattern) then
error('pattern matches empty string!', 2)
end
return coroutine.wrap(function()
local index = 1
repeat
local first, last = self:find(pattern, index)
if first and last then
if index < first then
coroutine.yield(self:sub(index, first - 1))
end
if capture then
coroutine.yield(self:sub(first, last))
end
index = last + 1
else
if index <= #self then
coroutine.yield(self:sub(index))
end
break
end
until index > #self
end)
end
function TextBoxWidget:_getVerticalList(alg)
if self.vertical_list then
return self.vertical_list
end
-- build horizontal list
local h_list = {}
local line_count = 0
for line in self.text:gsplit("\n", true) do
for words in line:gmatch("[\32-\127\192-\255]+[\128-\191]*") do
for word in words:gsplit("%s+", true) do
for w in word:gsplit("%p+", true) do
local word_box = {}
word_box.word = w
word_box.width = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, w, true, self.bold).x
table.insert(h_list, word_box)
end
end
end
if line:sub(-1) == "\n" then table.insert(h_list, {word = '\n', width = 0}) end
end
-- @TODO check alg here 25.04 2012 (houqp)
-- @TODO replace greedy algorithm with K&P algorithm 25.04 2012 (houqp)
self.vertical_list = self:_wrapGreedyAlg(h_list)
return self.vertical_list
end
function TextBoxWidget:_getCurrentVerticalList()
local line_height = (1 + self.line_height) * self.face.size
local v_list = self:_getVerticalList()
local current_v_list = {}
local height = 0
for i = self.first_line, #v_list do
if height < self.height - line_height then
table.insert(current_v_list, v_list[i])
height = height + line_height
else
break
end
end
return current_v_list
end
function TextBoxWidget:_getPreviousVerticalList()
local line_height = (1 + self.line_height) * self.face.size
local v_list = self:_getVerticalList()
local previous_v_list = {}
local height = 0
if self.first_line == 1 then
return self:_getCurrentVerticalList()
end
self.virtual_line = self.first_line
for i = self.first_line - 1, 1, -1 do
if height < self.height - line_height then
table.insert(previous_v_list, 1, v_list[i])
height = height + line_height
self.virtual_line = self.virtual_line - 1
else
break
end
end
for i = self.first_line, #v_list do
if height < self.height - line_height then
table.insert(previous_v_list, v_list[i])
height = height + line_height
else
break
end
end
if self.first_line > #previous_v_list then
self.first_line = self.first_line - #previous_v_list
else
self.first_line = 1
end
return previous_v_list
end
function TextBoxWidget:_getNextVerticalList()
local line_height = (1 + self.line_height) * self.face.size
local v_list = self:_getVerticalList()
local current_v_list = self:_getCurrentVerticalList()
local next_v_list = {}
local height = 0
if self.first_line + #current_v_list > #v_list then
return current_v_list
end
self.virtual_line = self.first_line
for i = self.first_line + #current_v_list, #v_list do
if height < self.height - line_height then
table.insert(next_v_list, v_list[i])
height = height + line_height
self.virtual_line = self.virtual_line + 1
else
break
end
end
self.first_line = self.first_line + #current_v_list
return next_v_list
end
function TextBoxWidget:_render(v_list)
local font_height = self.face.size
local line_height_px = self.line_height * font_height
local space_w = RenderText:sizeUtf8Text(0, Screen:getWidth(), self.face, " ", true).x
local h = (font_height + line_height_px) * #v_list
self._bb = Blitbuffer.new(self.width, h)
self._bb:fill(Blitbuffer.COLOR_WHITE)
local y = font_height
local pen_x = 0
for _,l in ipairs(v_list) do
pen_x = 0
for _,w in ipairs(l) do
--@TODO Don't use kerning for monospaced fonts. (houqp)
-- refert to cb25029dddc42693cc7aaefbe47e9bd3b7e1a750 in master tree
RenderText:renderUtf8Text(self._bb, pen_x, y, self.face, w.word, true, self.bold, self.fgcolor)
pen_x = pen_x + w.width
end
y = y + line_height_px + font_height
end
-- -- if text is shorter than one line, shrink to text's width
-- if #v_list == 1 then
-- self.width = pen_x
-- end
end
function TextBoxWidget:getVirtualLineNum()
return self.virtual_line
end
function TextBoxWidget:getAllLineCount()
local v_list = self:_getVerticalList()
return #v_list
end
function TextBoxWidget:getVisLineCount()
local line_height = (1 + self.line_height) * self.face.size
return math.floor(self.height / line_height)
end
function TextBoxWidget:scrollDown()
local next_v_list = self:_getNextVerticalList()
self:free()
self:_render(next_v_list)
end
function TextBoxWidget:scrollUp()
local previous_v_list = self:_getPreviousVerticalList()
self:free()
self:_render(previous_v_list)
end
function TextBoxWidget:getSize()
if self.width and self.height then
return Geom:new{ w = self.width, h = self.height}
else
return Geom:new{ w = self.width, h = self._bb:getHeight()}
end
end
function TextBoxWidget:paintTo(bb, x, y)
bb:blitFrom(self._bb, x, y, 0, 0, self.width, self._bb:getHeight())
end
function TextBoxWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
return TextBoxWidget