ReaderHighlight: pdf multi-page highlights (#9850)

reviewable/pr9863/r1
hius07 1 year ago committed by GitHub
parent b5e7ade880
commit cd56dd2edf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -206,11 +206,15 @@ function ReaderBookmark:isBookmarkInPositionOrder(a, b)
if a.page == b.page then -- both bookmarks in the same page
if a.highlighted and b.highlighted then -- both are highlights, compare positions
local is_reflow = self.ui.document.configurable.text_wrap -- save reflow mode
-- reflow mode doesn't set page in positions
a.pos0.page = a.page
a.pos1.page = a.page
b.pos0.page = b.page
b.pos1.page = b.page
-- reflow mode didn't set page in positions (in older bookmarks)
if not a.pos0.page then
a.pos0.page = a.page
a.pos1.page = a.page
end
if not b.pos0.page then
b.pos0.page = b.page
b.pos1.page = b.page
end
self.ui.document.configurable.text_wrap = 0 -- native positions
-- sort start and end positions of each highlight
local compare_pos, a_start, a_end, b_start, b_end, result
@ -1022,10 +1026,8 @@ function ReaderBookmark:renameBookmark(item, from_highlight, is_new_note, new_te
local bookmark
if from_highlight then
-- Called by ReaderHighlight:editHighlight, we need to find the bookmark
local pboxes = item.pboxes
for __, bm in ipairs(self.bookmarks) do
if item.datetime == bm.datetime and item.page == bm.page then
bm.pboxes = pboxes
if bm.text == nil or bm.text == "" then
bm.text = self:getBookmarkAutoText(bm)
end
@ -1093,13 +1095,7 @@ function ReaderBookmark:renameBookmark(item, from_highlight, is_new_note, new_te
if bookmark.datetime == bm.datetime and bookmark.page == bm.page then
bm.text = value
self.ui:handleEvent(Event:new("BookmarkEdited", bm))
-- A bookmark isn't necessarily a highlight (it doesn't have pboxes)
if bookmark.pboxes then
local setting = G_reader_settings:readSetting("save_document")
if setting ~= "disable" then
self.ui.document:updateHighlightContents(bookmark.page, bookmark, bookmark.text)
end
end
self.ui.highlight:writePdfAnnotation("content", bookmark.page, bookmark, bookmark.text)
break
end
end
@ -1438,7 +1434,7 @@ end
function ReaderBookmark:getBookmarkNote(item)
for _, bm in ipairs(self.bookmarks) do
if item.datetime == bm.datetime and item.page == bm.page then
if item.datetime == bm.datetime then
return not self:isBookmarkAutoText(bm) and bm.text
end
end

@ -648,17 +648,22 @@ end
function ReaderHighlight:onTapPageSavedHighlight(ges)
local pages = self.view:getCurrentPageList()
local pos = self.view:screenToPageTransform(ges.pos)
for key, page in pairs(pages) do
local items = self.view.highlight.saved[page]
for _, page in ipairs(pages) do
local items = self.view:getPageSavedHighlights(page)
if items then
for i = 1, #items do
local pos0, pos1 = items[i].pos0, items[i].pos1
local boxes = self.ui.document:getPageBoxesFromPositions(page, pos0, pos1)
for i, item in ipairs(items) do
local boxes = self.ui.document:getPageBoxesFromPositions(page, item.pos0, item.pos1)
if boxes then
for index, box in pairs(boxes) do
for _, box in ipairs(boxes) do
if inside_box(pos, box) then
logger.dbg("Tap on highlight")
return self:onShowHighlightNoteOrDialog(page, i)
local hl_page, hl_i
if item.parent then -- multi-page highlight
hl_page, hl_i = unpack(item.parent)
else
hl_page, hl_i = page, i
end
return self:onShowHighlightNoteOrDialog(hl_page, hl_i)
end
end
end
@ -816,10 +821,7 @@ function ReaderHighlight:onShowHighlightNoteOrDialog(page, index)
return true
end
local item = self.view.highlight.saved[page][index]
local bookmark_note = self.ui.bookmark:getBookmarkNote({
page = self.ui.document.info.has_pages and item.pos0.page or item.pos0,
datetime = item.datetime,
})
local bookmark_note = self.ui.bookmark:getBookmarkNote({datetime = item.datetime})
if bookmark_note then
local textviewer
textviewer = TextViewer:new{
@ -1512,20 +1514,13 @@ function ReaderHighlight:onHoldRelease()
if self.select_mode then -- extended highlighting, ending fragment
if self.selected_text then
if self.ui.paging and self.hold_pos.page ~= self.highlight_page then
self.ui.paging:_gotoPage(self.highlight_page)
UIManager:show(Notification:new{
text = _("Fragments must be within one page"),
})
self.select_mode = false
self:extendSelection()
if default_highlight_action == "select" then
self:saveHighlight(true)
self:clear()
else
self.select_mode = false
self:extendSelection()
if default_highlight_action == "select" then
self:saveHighlight(true)
self:clear()
else
self:onShowHighlightMenu()
end
self:onShowHighlightMenu()
end
end
return true
@ -1731,7 +1726,7 @@ function ReaderHighlight:getHighlightBookmarkItem()
end
if self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
return {
page = self.ui.paging and self.hold_pos.page or self.selected_text.pos0,
page = self.ui.paging and self.selected_text.pos0.page or self.selected_text.pos0,
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
notes = cleanupSelectedText(self.selected_text.text),
@ -1743,14 +1738,14 @@ end
function ReaderHighlight:saveHighlight(extend_to_sentence)
self.ui:handleEvent(Event:new("AddHighlight"))
logger.dbg("save highlight")
if self.hold_pos and self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
if self.selected_text and self.selected_text.pos0 and self.selected_text.pos1 then
if extend_to_sentence and self.ui.rolling then
local extended_text = self.ui.document:extendXPointersToSentenceSegment(self.selected_text.pos0, self.selected_text.pos1)
if extended_text then
self.selected_text = extended_text
end
end
local page = self.hold_pos.page
local page = self.ui.paging and self.selected_text.pos0.page or self.ui.document:getPageFromXPointer(self.selected_text.pos0)
if not self.view.highlight.saved[page] then
self.view.highlight.saved[page] = {}
end
@ -1763,6 +1758,7 @@ function ReaderHighlight:saveHighlight(extend_to_sentence)
pos0 = self.selected_text.pos0,
pos1 = self.selected_text.pos1,
pboxes = self.selected_text.pboxes,
ext = self.selected_text.ext,
drawer = self.view.highlight.saved_drawer,
chapter = chapter_name,
}
@ -1773,18 +1769,52 @@ function ReaderHighlight:saveHighlight(extend_to_sentence)
bookmark_item.chapter = chapter_name
self.ui.bookmark:addBookmark(bookmark_item)
end
if self.selected_text.pboxes then
self:exportToDocument(page, hl_item)
end
self:writePdfAnnotation("save", page, hl_item)
return page, #self.view.highlight.saved[page]
end
end
function ReaderHighlight:exportToDocument(page, item)
local setting = G_reader_settings:readSetting("save_document")
if setting == "disable" then return end
logger.dbg("export highlight to document", item)
local can_write = self.ui.document:saveHighlight(page, item)
function ReaderHighlight:writePdfAnnotation(action, page, item, content)
if self.ui.rolling or G_reader_settings:readSetting("save_document") == "disable" then
return
end
logger.dbg("write to pdf document", action, item)
local function doAction(_action, _page, _item, _content)
if _action == "save" then
return self.ui.document:saveHighlight(_page, _item)
elseif _action == "delete" then
return self.ui.document:deleteHighlight(_page, _item)
elseif _action == "content" then
return self.ui.document:updateHighlightContents(_page, _item, _content)
end
end
local can_write
if item.pos0.page == item.pos1.page then -- single-page highlight
local item_
if item.pboxes then
item_ = item
else -- called from bookmarks to write bookmark note to annotation
for _, hl in ipairs(self.view.highlight.saved[page]) do
if hl.datetime == item.datetime then
item_ = {pboxes = hl.pboxes}
break
end
end
end
can_write = doAction(action, page, item_, content)
else -- multi-page highlight
local is_reflow = self.ui.document.configurable.text_wrap
for hl_page = item.pos0.page, item.pos1.page do
self.ui.document.configurable.text_wrap = 0
local hl_part = self:getSavedExtendedHighlightPage(item, hl_page)
self.ui.document.configurable.text_wrap = is_reflow
can_write = doAction(action, hl_page, hl_part, content)
if can_write == false then break end
if action == "save" then -- update pboxes from quadpoints
item.ext[hl_page].pboxes = hl_part.pboxes
end
end
end
if can_write == false and not self.warned_once then
self.warned_once = true
UIManager:show(InfoMessage:new{
@ -1851,11 +1881,7 @@ function ReaderHighlight:deleteHighlight(page, i, bookmark_item)
datetime = removed.datetime,
})
end
local setting = G_reader_settings:readSetting("save_document")
if setting ~= "disable" then
logger.dbg("delete highlight from document", removed)
self.ui.document:deleteHighlight(page, removed)
end
self:writePdfAnnotation("delete", page, removed)
UIManager:setDirty(self.dialog, "ui")
end
@ -1864,13 +1890,11 @@ function ReaderHighlight:editHighlight(page, i, is_new_note, text)
self.ui.bookmark:renameBookmark({
page = self.ui.document.info.has_pages and page or item.pos0,
datetime = item.datetime,
pboxes = item.pboxes
}, true, is_new_note, text)
end
function ReaderHighlight:editHighlightStyle(page, i)
local item = self.view.highlight.saved[page][i]
local save_document = self.ui.paging and G_reader_settings:readSetting("save_document") ~= "disable"
local radio_buttons = {}
for _, v in ipairs(highlight_style) do
table.insert(radio_buttons, {
@ -1889,12 +1913,12 @@ function ReaderHighlight:editHighlightStyle(page, i)
default_provider = self.view.highlight.saved_drawer or
G_reader_settings:readSetting("highlight_drawing_style", "lighten"),
callback = function(radio)
if save_document then
self.ui.document:deleteHighlight(page, item)
end
self:writePdfAnnotation("delete", page, item)
item.drawer = radio.provider
if save_document then
self.ui.document:saveHighlight(page, item)
self:writePdfAnnotation("save", page, item)
local bm_note = self.ui.bookmark:getBookmarkNote(item)
if bm_note then
self:writePdfAnnotation("content", page, item, bm_note)
end
UIManager:setDirty(self.dialog, "ui")
self.ui:handleEvent(Event:new("BookmarkUpdated",
@ -1917,15 +1941,10 @@ function ReaderHighlight:extendSelection()
local item1 = self.view.highlight.saved[self.highlight_page][self.highlight_idx]
local item2_pos0, item2_pos1 = self.selected_text.pos0, self.selected_text.pos1
-- getting starting and ending positions, text and pboxes of extended highlight
local new_pos0, new_pos1, new_text, new_pboxes
if self.ui.document.info.has_pages then
local new_pos0, new_pos1, new_text, new_pboxes, ext
if self.ui.paging then
local cur_page = self.hold_pos.page
local is_reflow = self.ui.document.configurable.text_wrap
local new_page = self.hold_pos.page
-- reflow mode doesn't set page in positions
item1.pos0.page = new_page
item1.pos1.page = new_page
item2_pos0.page = new_page
item2_pos1.page = new_page
-- pos0 and pos1 are not in order within highlights, hence sorting all
local function comparePositions (pos1, pos2)
return self.ui.document:comparePositions(pos1, pos2) == 1
@ -1935,17 +1954,35 @@ function ReaderHighlight:extendSelection()
table.sort(positions, comparePositions)
new_pos0 = positions[1]
new_pos1 = positions[4]
local text_boxes = self.ui.document:getTextFromPositions(new_pos0, new_pos1)
new_text = text_boxes.text
new_pboxes = text_boxes.pboxes
local temp_pos0, temp_pos1
if new_pos0.page == new_pos1.page then -- single-page highlight
local text_boxes = self.ui.document:getTextFromPositions(new_pos0, new_pos1)
new_text = text_boxes.text
new_pboxes = text_boxes.pboxes
temp_pos0, temp_pos1 = new_pos0, new_pos1
else -- multi-page highlight
new_text = ""
ext = {}
for page = new_pos0.page, new_pos1.page do
local item = self:getExtendedHighlightPage(new_pos0, new_pos1, page)
new_text = new_text .. item.text
ext[page] = { -- for every page of multi-page highlight
pos0 = item.pos0,
pos1 = item.pos1,
pboxes = item.pboxes,
}
if page == cur_page then
temp_pos0, temp_pos1 = item.pos0, item.pos1
end
end
end
self.ui.document.configurable.text_wrap = is_reflow -- restore reflow
-- draw
self.view.highlight.temp[new_page] = self.ui.document:getPageBoxesFromPositions(new_page, new_pos0, new_pos1)
self.view.highlight.temp[cur_page] = self.ui.document:getPageBoxesFromPositions(cur_page, temp_pos0, temp_pos1)
else
-- pos0 and pos1 are in order within highlights
new_pos0 = self.ui.document:compareXPointers(item1.pos0, item2_pos0) == 1 and item1.pos0 or item2_pos0
new_pos1 = self.ui.document:compareXPointers(item1.pos1, item2_pos1) == 1 and item2_pos1 or item1.pos1
self.hold_pos.page = self.ui.document:getPageFromXPointer(new_pos0)
-- true to draw
new_text = self.ui.document:getTextFromXPointers(new_pos0, new_pos1, true)
end
@ -1955,10 +1992,81 @@ function ReaderHighlight:extendSelection()
pos0 = new_pos0,
pos1 = new_pos1,
pboxes = new_pboxes,
ext = ext,
}
UIManager:setDirty(self.dialog, "ui")
end
-- Calculates positions, text, pboxes of one page of selected multi-page highlight
-- (For pdf documents only, reflow mode must be off)
function ReaderHighlight:getExtendedHighlightPage(pos0, pos1, cur_page)
local item = {}
for page = pos0.page, pos1.page do
if page == cur_page then
local page_boxes = self.ui.document:getTextBoxes(page)
if page == pos0.page then
-- first page (from the start of highlight to the end of the page)
item.pos0 = pos0
item.pos1 = {
x = page_boxes[#page_boxes][#page_boxes[#page_boxes]].x1,
y = page_boxes[#page_boxes][#page_boxes[#page_boxes]].y1,
}
elseif page ~= pos1.page then
-- middle pages (full pages)
item.pos0 = {
x = page_boxes[1][1].x0,
y = page_boxes[1][1].y0,
}
item.pos1 = {
x = page_boxes[#page_boxes][#page_boxes[#page_boxes]].x1,
y = page_boxes[#page_boxes][#page_boxes[#page_boxes]].y1,
}
else
-- last page (from the start of the page to the end of highlight)
item.pos0 = {
x = page_boxes[1][1].x0,
y = page_boxes[1][1].y0,
}
item.pos1 = pos1
end
item.pos0.page = page
item.pos1.page = page
local text_boxes = self.ui.document:getTextFromPositions(item.pos0, item.pos1)
item.text = text_boxes.text
item.pboxes = text_boxes.pboxes
end
end
return item
end
-- Returns one page of saved multi-page highlight
-- (For pdf documents only)
function ReaderHighlight:getSavedExtendedHighlightPage(hl_or_bm, page, index)
local highlight
if hl_or_bm.ext then
highlight = hl_or_bm
else -- called from bookmark, need to find the corresponding highlight
for _, hl in ipairs(self.view.highlight.saved[hl_or_bm.page]) do
if hl.datetime == hl_or_bm.datetime then
highlight = hl
break
end
end
end
local item = {}
item.datetime = highlight.datetime
item.drawer = highlight.drawer
item.pos0 = highlight.ext[page].pos0
item.pos0.zoom = highlight.pos0.zoom
item.pos0.rotation = highlight.pos0.rotation
item.pos1 = highlight.ext[page].pos1
item.pos1.zoom = highlight.pos0.zoom
item.pos1.rotation = highlight.pos0.rotation
item.pboxes = highlight.ext[page].pboxes
item.parent = {highlight.pos0.page, index}
return item
end
function ReaderHighlight:onReadSettings(config)
self.view.highlight.saved_drawer = config:readSetting("highlight_drawer")
or G_reader_settings:readSetting("highlight_drawing_style") or self.view.highlight.saved_drawer

@ -199,12 +199,15 @@ function ReaderThumbnail:resetCachedPagesForBookmarks(...)
end
else
if bm.page and type(bm.page) == "number" then
local p = bm.page
if not start_page or p < start_page then
start_page = p
end
if not end_page or p > end_page then
end_page = p
local bm_page0 = (bm.pos0 and bm.pos0.page) or bm.page
local bm_page1 = (bm.pos1 and bm.pos1.page) or bm.page
for p = bm_page0, bm_page1 do
if not start_page or p < start_page then
start_page = p
end
if not end_page or p > end_page then
end_page = p
end
end
end
end

@ -506,32 +506,53 @@ function ReaderView:drawSavedHighlight(bb, x, y)
end
end
-- Returns the list of highlights in page.
-- The list includes full single-page highlights and parts of multi-page highlights.
function ReaderView:getPageSavedHighlights(page)
local highlights = {}
local is_reflow = self.document.configurable.text_wrap
self.document.configurable.text_wrap = 0
for page_num, page_highlights in pairs(self.highlight.saved) do
for i, highlight in ipairs(page_highlights) do
-- old single-page reflow highlights do not have page in position
local pos0_page = highlight.pos0.page or page_num
local pos1_page = highlight.pos1.page or page_num
if pos0_page <= page and page <= pos1_page then
if pos0_page == pos1_page then -- single-page highlight
table.insert(highlights, highlight)
else -- multi-page highlight
local item = self.ui.highlight:getSavedExtendedHighlightPage(highlight, page, i)
table.insert(highlights, item)
end
end
end
end
self.document.configurable.text_wrap = is_reflow
return highlights
end
function ReaderView:drawPageSavedHighlight(bb, x, y)
local pages = self:getCurrentPageList()
for _, page in pairs(pages) do
local items = self.highlight.saved[page]
if items then
for i = 1, #items do
local item = items[i]
local pos0, pos1 = item.pos0, item.pos1
local boxes = self.document:getPageBoxesFromPositions(page, pos0, pos1)
if boxes then
local drawer = item.drawer or self.highlight.saved_drawer
local draw_note_mark = self.highlight.note_mark and
self.ui.bookmark:getBookmarkNote({ page = page, datetime = item.datetime, })
for _, box in pairs(boxes) do
local rect = self:pageToScreenTransform(page, box)
if rect then
self:drawHighlightRect(bb, x, y, rect, drawer, draw_note_mark)
if draw_note_mark and self.highlight.note_mark == "sidemark" then
draw_note_mark = false -- side mark in the first line only
end
for _, page in ipairs(pages) do
local items = self:getPageSavedHighlights(page)
for _, item in ipairs(items) do
local boxes = self.document:getPageBoxesFromPositions(page, item.pos0, item.pos1)
if boxes then
local drawer = item.drawer or self.highlight.saved_drawer
local draw_note_mark = self.highlight.note_mark and
self.ui.bookmark:getBookmarkNote({datetime = item.datetime})
for _, box in ipairs(boxes) do
local rect = self:pageToScreenTransform(page, box)
if rect then
self:drawHighlightRect(bb, x, y, rect, drawer, draw_note_mark)
if draw_note_mark and self.highlight.note_mark == "sidemark" then
draw_note_mark = false -- side mark in the first line only
end
end -- end for each box
end -- end if boxes
end -- end for each highlight
end
end
end
end
end -- end for each page
end
end
function ReaderView:drawXPointerSavedHighlight(bb, x, y)
@ -567,11 +588,13 @@ function ReaderView:drawXPointerSavedHighlight(bb, x, y)
if boxes then
local drawer = item.drawer or self.highlight.saved_drawer
local draw_note_mark = self.highlight.note_mark and
self.ui.bookmark:getBookmarkNote({ page = item.pos0, datetime = item.datetime, })
for _, box in pairs(boxes) do
self:drawHighlightRect(bb, x, y, box, drawer, draw_note_mark)
if draw_note_mark and self.highlight.note_mark == "sidemark" then
draw_note_mark = false -- side mark in the first line only
self.ui.bookmark:getBookmarkNote({datetime = item.datetime})
for _, box in ipairs(boxes) do
if box.h ~= 0 then
self:drawHighlightRect(bb, x, y, box, drawer, draw_note_mark)
if draw_note_mark and self.highlight.note_mark == "sidemark" then
draw_note_mark = false -- side mark in the first line only
end
end
end -- end for each box
end -- end if boxes

@ -1184,7 +1184,7 @@ Transform position in native page to reflowed page.
]]--
function KoptInterface:nativeToReflowPosTransform(doc, pageno, pos)
local kc = self:getCachedContext(doc, pageno)
local rpos = {}
local rpos = {page = pageno}
rpos.x, rpos.y = kc:nativeToReflowPosTransform(pos.x, pos.y)
return rpos
end
@ -1194,7 +1194,7 @@ Transform position in reflowed page to native page.
]]--
function KoptInterface:reflowToNativePosTransform(doc, pageno, abs_pos, rel_pos)
local kc = self:getCachedContext(doc, pageno)
local npos = {}
local npos = {page = pageno}
npos.x, npos.y = kc:reflowToNativePosTransform(abs_pos.x, abs_pos.y, rel_pos.x, rel_pos.y)
return npos
end
@ -1294,6 +1294,11 @@ Returns 1 if positions are ordered (if ppos2 is after ppos1), -1 if not, 0 if sa
Positions of the word boxes containing ppos1 and ppos2 are compared.
--]]
function KoptInterface:comparePositions(doc, ppos1, ppos2)
if ppos1.page < ppos2.page then
return 1
elseif ppos1.page > ppos2.page then
return -1
end
local box1 = self:getWordFromPosition(doc, ppos1).pbox
local box2 = self:getWordFromPosition(doc, ppos2).pbox
if box1.y == box2.y then

@ -121,6 +121,10 @@ function PdfDocument:getTextFromPositions(spos0, spos1)
return self.koptinterface:getTextFromPositions(self, spos0, spos1)
end
function PdfDocument:getTextBoxes(pageno)
return self.koptinterface:getTextBoxes(self, pageno)
end
function PdfDocument:getPageBoxesFromPositions(pageno, ppos0, ppos1)
return self.koptinterface:getPageBoxesFromPositions(self, pageno, ppos0, ppos1)
end

Loading…
Cancel
Save