2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00

add highlight in pdf reader

This commit is contained in:
chrox 2013-06-15 23:13:19 +08:00
parent eb2e26160a
commit eb4c76bd15
3 changed files with 451 additions and 75 deletions

View File

@ -1,3 +1,4 @@
require "ui/widget/buttontable"
ReaderHighlight = InputContainer:new{}
@ -33,6 +34,27 @@ function ReaderHighlight:initGesListener()
}
}
},
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
@ -44,91 +66,302 @@ function ReaderHighlight:onSetDimensions(dimen)
end
function ReaderHighlight:onTap(arg, ges)
if self.view.highlight.rect then
self.view.highlight.rect = nil
local function inside_box(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 _, 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(items[i].boxes[j]) then
DEBUG("Tap on hightlight")
self.edit_highlight_dialog = ButtonTable:new{
buttons = {
{
{
text="Delete",
callback = function() self:deleteHighlight(page, i) end,
},
{
text="Edit",
callback = function() self:editHighlight() end,
},
},
},
tap_close_callback = function() self.ui:handleEvent(Event:new("Tap")) end,
}
UIManager:show(self.edit_highlight_dialog)
return true
end
end
end
end
end
function ReaderHighlight:onHold(arg, ges)
self.pos = self.view:screenToPageTransform(ges.pos)
DEBUG("hold position in page", self.pos)
if not self.pos then
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
local text_boxes = self.ui.document:getTextBoxes(self.pos.page)
--DEBUG("page text", text_boxes)
self.page_boxes = self.ui.document:getTextBoxes(self.hold_pos.page)
--DEBUG("page text", page_boxes)
if not text_boxes or #text_boxes == 0 then
DEBUG("no text box detected")
if not self.page_boxes or #self.page_boxes == 0 then
DEBUG("no page boxes detected")
return true
end
self.word_info = self:getWordFromBoxes(text_boxes, self.pos)
DEBUG("hold word info in page", self.word_info)
if self.word_info then
-- if we extracted text directly
if self.word_info.word then
self.ui:handleEvent(Event:new("LookupWord", self.word_info.word))
-- or we will do OCR
else
UIManager:scheduleIn(0.1, function()
local word_box = self.word_info.box
word_box.x = word_box.x - math.floor(word_box.h * 0.2)
word_box.y = word_box.y - math.floor(word_box.h * 0.4)
word_box.w = word_box.w + math.floor(word_box.h * 0.4)
word_box.h = word_box.h + math.floor(word_box.h * 0.6)
local word = self.ui.document:getOCRWord(self.pos.page, word_box)
DEBUG("OCRed word:", word)
self.ui:handleEvent(Event:new("LookupWord", word))
end)
end
local screen_rect = self.view:pageToScreenTransform(self.pos.page, self.word_info.box)
DEBUG("highlight word rect", screen_rect)
if screen_rect then
screen_rect.x = screen_rect.x - screen_rect.h * 0.2
screen_rect.y = screen_rect.y - screen_rect.h * 0.2
screen_rect.w = screen_rect.w + screen_rect.h * 0.4
screen_rect.h = screen_rect.h + screen_rect.h * 0.4
self.view.highlight.rect = screen_rect
UIManager:setDirty(self.dialog, "partial")
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:getWordFromBoxes(boxes, pos)
local function ges_inside(x0, y0, x1, y1)
function ReaderHighlight:onHoldPan(arg, ges)
self.selected_word = nil
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
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() end,
},
{
text="Add Note",
callback = function() self:addNote() end,
},
},
{
{
text="Share",
callback = function() self:shareHighlight() end,
},
{
text="More",
callback = function() self:moreAction() 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")
UIManager:close(self.highlight_dialog)
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
table.insert(self.view.highlight.saved[page], hl_item)
end
--DEBUG("saved hightlights", self.view.highlight.saved[page])
end
function ReaderHighlight:addNote()
DEBUG("add Note")
UIManager:close(self.highlight_dialog)
end
function ReaderHighlight:shareHighlight()
DEBUG("share highlight")
UIManager:close(self.highlight_dialog)
end
function ReaderHighlight:moreAction()
DEBUG("more action")
UIManager:close(self.highlight_dialog)
end
function ReaderHighlight:deleteHighlight(page, i)
DEBUG("delete highlight")
UIManager:close(self.edit_highlight_dialog)
table.remove(self.view.highlight.saved[page], i)
end
function ReaderHighlight:editHighlight()
DEBUG("edit highlight")
UIManager:close(self.edit_highlight_dialog)
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 x0 ~= nil and y0 ~= nil and x1 ~= nil and y1 ~= nil then
if x0 <= x and y0 <= y and x1 >= x and y1 >= y then
return true
end
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
local l = boxes[i]
if ges_inside(l.x0, l.y0, l.x1, l.y1) then
--DEBUG("line box", l.x0, l.y0, l.x1, l.y1)
for j = 1, #boxes[i] do
local w = boxes[i][j]
if ges_inside(w.x0, w.y0, w.x1, w.y1) then
local box = Geom:new{
x = w.x0, y = w.y0,
w = w.x1 - w.x0,
h = w.y1 - w.y0,
}
return {
word = w.word,
box = box,
}
end -- end if inside word box
end -- end for each word
end -- end if inside line box
end -- end for each line
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

View File

@ -18,8 +18,12 @@ ReaderView = OverlapGroup:new{
outer_page_color = 0,
-- hightlight
highlight = {
drawer = "marker" -- show as inverted block instead of underline
temp_drawer = "invert",
temp = {},
saved_drawer = "lighten",
saved = {},
},
highlight_visible = true,
-- PDF/DjVu continuous paging
page_scroll = nil,
page_bgcolor = 0,
@ -101,9 +105,14 @@ function ReaderView:paintTo(bb, x, y)
)
end
-- draw highlight
if self.highlight.rect then
self:drawHightlight(bb, x, y, self.highlight.rect)
-- draw saved highlight
if self.highlight_visible then
self:drawSavedHighlight(bb, x, y)
end
-- draw temporary highlight
if self.highlight.temp then
self:drawTempHighlight(bb, x, y)
end
-- paint dogear
@ -190,6 +199,20 @@ function ReaderView:drawScrollPages(bb, x, y)
end)
end
function ReaderView:getCurrentPageList()
local pages = {}
if self.ui.document.info.has_pages then
if self.page_scroll then
for _, state in ipairs(self.page_states) do
table.insert(pages, state.page)
end
else
table.insert(pages, self.state.page)
end
end
return pages
end
function ReaderView:getScrollPagePosition(pos)
local x_s, y_s = pos.x, pos.y
local x_p, y_p = nil, nil
@ -213,7 +236,7 @@ end
function ReaderView:getScrollPageRect(page, rect_p)
local rect_s = Geom:new{}
for _, state in ipairs(self.page_states) do
local trans_p = rect_p:copy()
local trans_p = Geom:new(rect_p)
trans_p:transformByScale(state.zoom, state.zoom)
if page == state.page and state.visible_area:contains(trans_p) then
rect_s.x = rect_s.x + state.offset.x + trans_p.x - state.visible_area.x
@ -263,7 +286,7 @@ end
function ReaderView:getSinglePageRect(rect_p)
local rect_s = Geom:new{}
local trans_p = rect_p:copy()
local trans_p = Geom:new(rect_p)
trans_p:transformByScale(self.state.zoom, self.state.zoom)
if self.visible_area:contains(trans_p) then
rect_s.x = self.state.offset.x + trans_p.x - self.visible_area.x
@ -292,17 +315,45 @@ function ReaderView:drawScrollView(bb, x, y)
self.state.pos)
end
function ReaderView:drawHightlight(bb, x, y, rect)
function ReaderView:drawTempHighlight(bb, x, y)
for page, boxes in pairs(self.highlight.temp) do
for i = 1, #boxes do
local rect = self:pageToScreenTransform(page, boxes[i])
if rect then
self:drawHighlightRect(bb, x, y, rect, self.highlight.temp_drawer)
end
end
end
end
function ReaderView:drawSavedHighlight(bb, x, y)
local pages = self:getCurrentPageList()
for _, page in pairs(pages) do
local items = self.highlight.saved[page]
if not items then items = {} end
for i = 1, #items do
for j = 1, #items[i].boxes do
local rect = self:pageToScreenTransform(page, items[i].boxes[j])
if rect then
self:drawHighlightRect(bb, x, y, rect, self.highlight.saved_drawer)
end
end -- end for each box
end -- end for each hightlight
end -- end for each page
end
function ReaderView:drawHighlightRect(bb, x, y, rect, drawer)
local x, y, w, h = rect.x, rect.y, rect.w, rect.h
self.highlight.drawer = self.highlight.drawer or "underscore"
if self.highlight.drawer == "underscore" then
if drawer == "underscore" then
self.highlight.line_width = self.highlight.line_width or 2
self.highlight.line_color = self.highlight.line_color or 5
bb:paintRect(x, y+h-1, w,
self.highlight.line_width,
self.highlight.line_color)
elseif self.highlight.drawer == "marker" then
elseif drawer == "lighten" then
bb:lightenRect(x, y, w, h, 0.1)
elseif drawer == "invert" then
bb:invertRect(x, y, w, h)
end
end
@ -438,6 +489,7 @@ function ReaderView:onReadSettings(config)
page_scroll = self.document.configurable.page_scroll
end
self.page_scroll = page_scroll == 1 and true or false
self.highlight.saved = config:readSetting("highlight") or {}
end
function ReaderView:onPageUpdate(new_page_no)
@ -498,4 +550,5 @@ function ReaderView:onCloseDocument()
self.ui.doc_settings:saveSetting("render_mode", self.render_mode)
self.ui.doc_settings:saveSetting("screen_mode", self.screen_mode)
self.ui.doc_settings:saveSetting("gamma", self.state.gamma)
self.ui.doc_settings:saveSetting("highlight", self.highlight.saved)
end

View File

@ -0,0 +1,90 @@
require "ui/widget/base"
require "ui/widget/line"
ButtonTable = InputContainer:new{
buttons = {
{
{text="OK", callback=nil},
{text="Cancel", callback=nil},
},
},
tap_close_callback = nil,
}
function ButtonTable:init()
if Device:hasKeyboard() then
key_events = {
AnyKeyPressed = { { Input.group.Any },
seqtext = "any key", doc = _("close dialog") }
}
else
self.ges_events.TapClose = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
}
end
local vertical_group = VerticalGroup:new{}
local horizontal_sep = LineWidget:new{
background = 8,
dimen = Geom:new{
w = Screen:getWidth()*0.9,
h = 1,
}
}
for i = 1, #self.buttons do
local horizontal_group = HorizontalGroup:new{}
local line = self.buttons[i]
for j = 1, #line do
local button = Button:new{
text = line[j].text,
callback = line[j].callback,
width = Screen:getWidth()*0.9/#line,
bordersize = 0,
text_font_face = "cfont",
text_font_size = scaleByDPI(18),
}
local button_dim = button:getSize()
local vertical_sep = LineWidget:new{
background = 8,
dimen = Geom:new{
w = scaleByDPI(1),
h = button_dim.h,
}
}
table.insert(horizontal_group, button)
if j < #line then
table.insert(horizontal_group, vertical_sep)
end
end -- end for each button
table.insert(vertical_group, horizontal_group)
if i < #self.buttons then
table.insert(vertical_group, VerticalSpan:new{ width = scaleByDPI(2) })
table.insert(vertical_group, horizontal_sep)
table.insert(vertical_group, VerticalSpan:new{ width = scaleByDPI(2) })
end
end -- end for each button line
self[1] = CenterContainer:new{
dimen = Screen:getSize(),
FrameContainer:new{
vertical_group,
background = 0,
bordersize = 2,
radius = 7,
padding = 2,
},
}
end
function ButtonTable:onTapClose()
UIManager:close(self)
if self.tap_close_callback then
self.tap_close_callback()
end
return true
end