mirror of
https://github.com/koreader/koreader
synced 2024-10-31 21:20:20 +00:00
411 lines
10 KiB
Lua
411 lines
10 KiB
Lua
require "ui/widget/buttontable"
|
|
|
|
ReaderHighlight = InputContainer:new{}
|
|
|
|
function ReaderHighlight:init()
|
|
if Device:hasKeyboard() then
|
|
self.key_events = {
|
|
ShowToc = {
|
|
{ "." },
|
|
doc = _("highlight text") },
|
|
}
|
|
end
|
|
end
|
|
|
|
function ReaderHighlight:initGesListener()
|
|
self.ges_events = {
|
|
Tap = {
|
|
GestureRange:new{
|
|
ges = "tap",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight()
|
|
}
|
|
}
|
|
},
|
|
Hold = {
|
|
GestureRange:new{
|
|
ges = "hold",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight()
|
|
}
|
|
}
|
|
},
|
|
HoldRelease = {
|
|
GestureRange:new{
|
|
ges = "hold_release",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight()
|
|
}
|
|
}
|
|
},
|
|
HoldPan = {
|
|
GestureRange:new{
|
|
ges = "hold_pan",
|
|
range = Geom:new{
|
|
x = 0, y = 0,
|
|
w = Screen:getWidth(),
|
|
h = Screen:getHeight()
|
|
},
|
|
rate = 2.0,
|
|
}
|
|
},
|
|
}
|
|
end
|
|
|
|
function ReaderHighlight:onSetDimensions(dimen)
|
|
-- update listening according to new screen dimen
|
|
if Device:isTouchDevice() then
|
|
self:initGesListener()
|
|
end
|
|
end
|
|
|
|
function ReaderHighlight:onTap(arg, ges)
|
|
local function inside_box(ges, box)
|
|
local pos = self.view:screenToPageTransform(ges.pos)
|
|
local x, y = pos.x, pos.y
|
|
if box.x <= x and box.y <= y
|
|
and box.x + box.w >= x
|
|
and box.y + box.h >= y then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
if self.hold_pos then
|
|
self.view.highlight.temp[self.hold_pos.page] = nil
|
|
UIManager:setDirty(self.dialog, "partial")
|
|
self.hold_pos = nil
|
|
return true
|
|
end
|
|
local pages = self.view:getCurrentPageList()
|
|
for key, page in pairs(pages) do
|
|
local items = self.view.highlight.saved[page]
|
|
if not items then items = {} end
|
|
for i = 1, #items do
|
|
for j = 1, #items[i].boxes do
|
|
if inside_box(ges, items[i].boxes[j]) then
|
|
DEBUG("Tap on hightlight")
|
|
self.edit_highlight_dialog = ButtonTable:new{
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Delete"),
|
|
callback = function()
|
|
self:deleteHighlight(page, i)
|
|
UIManager:close(self.edit_highlight_dialog)
|
|
end,
|
|
},
|
|
{
|
|
text = _("Edit"),
|
|
enabled = false,
|
|
callback = function()
|
|
self:editHighlight()
|
|
UIManager:close(self.edit_highlight_dialog)
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
UIManager:show(self.edit_highlight_dialog)
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function ReaderHighlight:onHold(arg, ges)
|
|
self.hold_pos = self.view:screenToPageTransform(ges.pos)
|
|
DEBUG("hold position in page", self.hold_pos)
|
|
if not self.hold_pos then
|
|
DEBUG("not inside page area")
|
|
return true
|
|
end
|
|
self.page_boxes = self.ui.document:getTextBoxes(self.hold_pos.page)
|
|
--DEBUG("page text", page_boxes)
|
|
|
|
if not self.page_boxes or #self.page_boxes == 0 then
|
|
DEBUG("no page boxes detected")
|
|
return true
|
|
end
|
|
|
|
self.selected_word = self:getWordFromPosition(self.page_boxes, self.hold_pos)
|
|
DEBUG("selected word:", self.selected_word)
|
|
if self.selected_word then
|
|
local boxes = {}
|
|
table.insert(boxes, self.selected_word.box)
|
|
self.view.highlight.temp[self.hold_pos.page] = boxes
|
|
UIManager:setDirty(self.dialog, "partial")
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderHighlight:onHoldPan(arg, ges)
|
|
if not self.page_boxes or #self.page_boxes == 0 then
|
|
DEBUG("no page boxes detected")
|
|
return true
|
|
end
|
|
self.holdpan_pos = self.view:screenToPageTransform(ges.pos)
|
|
DEBUG("holdpan position in page", self.holdpan_pos)
|
|
self.selected_text = self:getTextFromPositions(self.page_boxes, self.hold_pos, self.holdpan_pos)
|
|
--DEBUG("selected text:", self.selected_text)
|
|
if self.selected_text then
|
|
self.view.highlight.temp[self.hold_pos.page] = self.selected_text.boxes
|
|
-- remove selected word if hold moves out of word box
|
|
if self.selected_word and
|
|
not self.selected_word.box:contains(self.selected_text.boxes[1]) then
|
|
self.selected_word = nil
|
|
end
|
|
UIManager:setDirty(self.dialog, "partial")
|
|
end
|
|
end
|
|
|
|
function ReaderHighlight:onHoldRelease(arg, ges)
|
|
if self.selected_word then
|
|
-- if we extracted text directly
|
|
if self.selected_word.word then
|
|
self.ui:handleEvent(Event:new("LookupWord", self.selected_word.word))
|
|
-- or we will do OCR
|
|
else
|
|
local word_box = self.selected_word.box
|
|
word_box.x = word_box.x - math.floor(word_box.h * 0.1)
|
|
word_box.y = word_box.y - math.floor(word_box.h * 0.2)
|
|
word_box.w = word_box.w + math.floor(word_box.h * 0.2)
|
|
word_box.h = word_box.h + math.floor(word_box.h * 0.4)
|
|
local word = self.ui.document:getOCRWord(self.hold_pos.page, word_box)
|
|
DEBUG("OCRed word:", word)
|
|
self.ui:handleEvent(Event:new("LookupWord", word))
|
|
end
|
|
self.selected_word = nil
|
|
elseif self.selected_text then
|
|
DEBUG("show highlight dialog")
|
|
self.highlight_dialog = ButtonTable:new{
|
|
buttons = {
|
|
{
|
|
{
|
|
text = _("Highlight"),
|
|
callback = function()
|
|
self:saveHighlight()
|
|
UIManager:close(self.highlight_dialog)
|
|
self.ui:handleEvent(Event:new("Tap"))
|
|
end,
|
|
},
|
|
{
|
|
text = _("Add Note"),
|
|
enabled = false,
|
|
callback = function()
|
|
self:addNote()
|
|
UIManager:close(self.highlight_dialog)
|
|
self.ui:handleEvent(Event:new("Tap"))
|
|
end,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
text = _("Share"),
|
|
enabled = false,
|
|
callback = function()
|
|
self:shareHighlight()
|
|
UIManager:close(self.highlight_dialog)
|
|
self.ui:handleEvent(Event:new("Tap"))
|
|
end,
|
|
},
|
|
{
|
|
text = _("More"),
|
|
enabled = false,
|
|
callback = function()
|
|
self:moreAction()
|
|
UIManager:close(self.highlight_dialog)
|
|
self.ui:handleEvent(Event:new("Tap"))
|
|
end,
|
|
},
|
|
},
|
|
},
|
|
tap_close_callback = function() self.ui:handleEvent(Event:new("Tap")) end,
|
|
}
|
|
UIManager:show(self.highlight_dialog)
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ReaderHighlight:saveHighlight()
|
|
DEBUG("save highlight")
|
|
local page = self.hold_pos.page
|
|
if self.hold_pos and self.selected_text then
|
|
if not self.view.highlight.saved[page] then
|
|
self.view.highlight.saved[page] = {}
|
|
end
|
|
local hl_item = {}
|
|
hl_item["text"] = self.selected_text.text
|
|
hl_item["boxes"] = self.selected_text.boxes
|
|
hl_item["datetime"] = os.date("%Y-%m-%d %H:%M:%S"),
|
|
table.insert(self.view.highlight.saved[page], hl_item)
|
|
if self.selected_text.text ~= "" then
|
|
self:exportToClippings(page, hl_item)
|
|
end
|
|
end
|
|
--DEBUG("saved hightlights", self.view.highlight.saved[page])
|
|
end
|
|
|
|
function ReaderHighlight:exportToClippings(page, item)
|
|
DEBUG("export highlight to My Clippings")
|
|
local clippings = io.open("/mnt/us/documents/My Clippings.txt", "a+")
|
|
if clippings then
|
|
local current_locale = os.setlocale()
|
|
os.setlocale("C")
|
|
clippings:write(self.document.file:gsub("(.*/)(.*)", "%2").."\n")
|
|
clippings:write("- Koreader Highlight Page "..page.." ")
|
|
clippings:write("| Added on "..os.date("%A, %b %d, %Y %I:%M:%S %p\n\n"))
|
|
clippings:write(item["text"].."\n")
|
|
clippings:write("==========\n")
|
|
clippings:close()
|
|
os.setlocale(current_locale)
|
|
end
|
|
end
|
|
|
|
function ReaderHighlight:addNote()
|
|
DEBUG("add Note")
|
|
end
|
|
|
|
function ReaderHighlight:shareHighlight()
|
|
DEBUG("share highlight")
|
|
end
|
|
|
|
function ReaderHighlight:moreAction()
|
|
DEBUG("more action")
|
|
end
|
|
|
|
function ReaderHighlight:deleteHighlight(page, i)
|
|
DEBUG("delete highlight")
|
|
table.remove(self.view.highlight.saved[page], i)
|
|
end
|
|
|
|
function ReaderHighlight:editHighlight()
|
|
DEBUG("edit highlight")
|
|
end
|
|
|
|
--[[
|
|
get index of nearest word box around pos
|
|
--]]
|
|
local function getWordBoxIndices(boxes, pos)
|
|
local function inside_box(box)
|
|
local x, y = pos.x, pos.y
|
|
if box.x0 <= x and box.y0 <= y and box.x1 >= x and box.y1 >= y then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
local function box_distance(i, j)
|
|
local wb = boxes[i][j]
|
|
if inside_box(wb) then
|
|
return 0
|
|
else
|
|
local x0, y0 = pos.x, pos.y
|
|
local x1, y1 = (wb.x0 + wb.x1) / 2, (wb.y0 + wb.y1) / 2
|
|
return (x0 - x1)*(x0 - x1) + (y0 - y1)*(y0 - y1)
|
|
end
|
|
end
|
|
|
|
local m, n = 1, 1
|
|
for i = 1, #boxes do
|
|
for j = 1, #boxes[i] do
|
|
if box_distance(i, j) < box_distance(m, n) then
|
|
m, n = i, j
|
|
end
|
|
end
|
|
end
|
|
return m, n
|
|
end
|
|
|
|
--[[
|
|
get word and word box around pos
|
|
--]]
|
|
function ReaderHighlight:getWordFromPosition(boxes, pos)
|
|
local i, j = getWordBoxIndices(boxes, pos)
|
|
local lb = boxes[i]
|
|
local wb = boxes[i][j]
|
|
if lb and wb then
|
|
local box = Geom:new{
|
|
x = wb.x0, y = lb.y0,
|
|
w = wb.x1 - wb.x0,
|
|
h = lb.y1 - lb.y0,
|
|
}
|
|
return {
|
|
word = wb.word,
|
|
box = box,
|
|
}
|
|
end
|
|
end
|
|
|
|
--[[
|
|
get text and text boxes between pos0 and pos1
|
|
--]]
|
|
function ReaderHighlight:getTextFromPositions(boxes, pos0, pos1)
|
|
local line_text = ""
|
|
local line_boxes = {}
|
|
local i_start, j_start = getWordBoxIndices(boxes, pos0)
|
|
local i_stop, j_stop = getWordBoxIndices(boxes, pos1)
|
|
if i_start == i_stop and j_start > j_stop or i_start > i_stop then
|
|
i_start, i_stop = i_stop, i_start
|
|
j_start, j_stop = j_stop, j_start
|
|
end
|
|
for i = i_start, i_stop do
|
|
-- insert line words
|
|
local j0 = i > i_start and 1 or j_start
|
|
local j1 = i < i_stop and #boxes[i] or j_stop
|
|
for j = j0, j1 do
|
|
local word = boxes[i][j].word
|
|
if word then
|
|
-- if last character of this word is an ascii char then append a space
|
|
local space = (word:match("[%z\194-\244][\128-\191]*$") or j == j1)
|
|
and "" or " "
|
|
line_text = line_text..word..space
|
|
end
|
|
end
|
|
-- insert line box
|
|
local lb = boxes[i]
|
|
if i > i_start and i < i_stop then
|
|
local line_box = Geom:new{
|
|
x = lb.x0, y = lb.y0,
|
|
w = lb.x1 - lb.x0,
|
|
h = lb.y1 - lb.y0,
|
|
}
|
|
table.insert(line_boxes, line_box)
|
|
elseif i == i_start and i < i_stop then
|
|
local wb = boxes[i][j_start]
|
|
local line_box = Geom:new{
|
|
x = wb.x0, y = lb.y0,
|
|
w = lb.x1 - wb.x0,
|
|
h = lb.y1 - lb.y0,
|
|
}
|
|
table.insert(line_boxes, line_box)
|
|
elseif i > i_start and i == i_stop then
|
|
local wb = boxes[i][j_stop]
|
|
local line_box = Geom:new{
|
|
x = lb.x0, y = lb.y0,
|
|
w = wb.x1 - lb.x0,
|
|
h = lb.y1 - lb.y0,
|
|
}
|
|
table.insert(line_boxes, line_box)
|
|
elseif i == i_start and i == i_stop then
|
|
local wb_start = boxes[i][j_start]
|
|
local wb_stop = boxes[i][j_stop]
|
|
local line_box = Geom:new{
|
|
x = wb_start.x0, y = lb.y0,
|
|
w = wb_stop.x1 - wb_start.x0,
|
|
h = lb.y1 - lb.y0,
|
|
}
|
|
table.insert(line_boxes, line_box)
|
|
end
|
|
end
|
|
return {
|
|
text = line_text,
|
|
boxes = line_boxes,
|
|
}
|
|
end
|