diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index eb228745c..259ae0a13 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -1028,8 +1028,25 @@ function ReaderHighlight:getHighlightVisibleBoxes(index) end function ReaderHighlight:updateHighlight(index, side, direction, move_by_char) + if move_by_char and self.ui.paging then return end local highlight = self.ui.annotation.annotations[index] local highlight_before = util.tableDeepCopy(highlight) + local is_updated + if self.ui.rolling then + is_updated = self:updateHighlightRolling(highlight, side, direction, move_by_char) + else + is_updated = self:updateHighlightPaging(highlight, side, direction) + end + if is_updated then + highlight.text = util.cleanupSelectedText(highlight.text) + self:writePdfAnnotation("delete", highlight_before) + self:writePdfAnnotation("save", highlight) + self.ui:handleEvent(Event:new("AnnotationsModified", { highlight, highlight_before })) + UIManager:setDirty(self.dialog, "ui") + end +end + +function ReaderHighlight:updateHighlightRolling(highlight, side, direction, move_by_char) local highlight_beginning = highlight.pos0 local highlight_end = highlight.pos1 if side == 0 then -- we move pos0 @@ -1040,22 +1057,20 @@ function ReaderHighlight:updateHighlight(index, side, direction, move_by_char) else updated_highlight_beginning = self.ui.document:getNextVisibleWordStart(highlight_beginning) end - else -- move highlight to the left + else -- move highlight to the left if move_by_char then updated_highlight_beginning = self.ui.document:getPrevVisibleChar(highlight_beginning) else updated_highlight_beginning = self.ui.document:getPrevVisibleWordStart(highlight_beginning) end end - if updated_highlight_beginning then - local order = self.ui.document:compareXPointers(updated_highlight_beginning, highlight_end) - if order and order > 0 then -- only if beginning did not go past end - highlight.pos0 = updated_highlight_beginning - highlight.page = updated_highlight_beginning - highlight.chapter = self.ui.toc:getTocTitleByPage(updated_highlight_beginning) - highlight.pageno = self.document:getPageFromXPointer(updated_highlight_beginning) - end - end + if updated_highlight_beginning == nil then return end + local order = self.ui.document:compareXPointers(updated_highlight_beginning, highlight_end) + if order == nil or order <= 0 then return end -- only if beginning did not go past end + highlight.pos0 = updated_highlight_beginning + highlight.page = updated_highlight_beginning + highlight.chapter = self.ui.toc:getTocTitleByPage(updated_highlight_beginning) + highlight.pageno = self.document:getPageFromXPointer(updated_highlight_beginning) else -- we move pos1 local updated_highlight_end if direction == 1 then -- move highlight to the right @@ -1071,19 +1086,15 @@ function ReaderHighlight:updateHighlight(index, side, direction, move_by_char) updated_highlight_end = self.ui.document:getPrevVisibleWordEnd(highlight_end) end end - if updated_highlight_end then - local order = self.ui.document:compareXPointers(highlight_beginning, updated_highlight_end) - if order and order > 0 then -- only if end did not go back past beginning - highlight.pos1 = updated_highlight_end - end - end + if updated_highlight_end == nil then return end + local order = self.ui.document:compareXPointers(highlight_beginning, updated_highlight_end) + if order == nil or order <= 0 then return end -- only if end did not go back past beginning + highlight.pos1 = updated_highlight_end end local new_beginning = highlight.pos0 local new_end = highlight.pos1 - local new_text = self.ui.document:getTextFromXPointers(new_beginning, new_end) - highlight.text = util.cleanupSelectedText(new_text) - self.ui:handleEvent(Event:new("AnnotationsModified", { highlight, highlight_before })) + highlight.text = self.ui.document:getTextFromXPointers(new_beginning, new_end) if side == 0 then -- Ensure we show the page with the new beginning of highlight if not self.ui.document:isXPointerInCurrentPage(new_beginning) then @@ -1104,7 +1115,147 @@ function ReaderHighlight:updateHighlight(index, side, direction, move_by_char) end end end - UIManager:setDirty(self.dialog, "ui") + return true +end + +function ReaderHighlight:updateHighlightPaging(highlight, side, direction) + local page = self.ui.paging.current_page + local pboxes + if highlight.ext then -- multipage highlight, don't move invisible boundaries + if (page ~= highlight.pos0.page and page ~= highlight.pos1.page ) or -- middle pages + (page == highlight.pos0.page and side == 1) or -- first page, tried to move end + (page == highlight.pos1.page and side == 0) then -- last page, tried to move start + return + end + pboxes = highlight.ext[page].pboxes + else + pboxes = highlight.pboxes + end + local page_boxes = self.document:getTextBoxes(page) + + -- find page boxes indices of the highlight start and end pboxes + -- pboxes { x, y, h, w }; page_boxes { x0, y0, x1, y1, word } + local start_i, start_j, end_i, end_j + local function is_equal(a, b) + return math.abs(a - b) < 0.001 + end + local start_box = pboxes[1] + local end_box = pboxes[#pboxes] + for i, line in ipairs(page_boxes) do + for j, box in ipairs(line) do + if not start_i and is_equal(start_box.x, box.x0) and is_equal(start_box.y, box.y0) then + start_i, start_j = i, j + end + if not end_i and is_equal(end_box.x + end_box.w, box.x1) and is_equal(end_box.y, box.y0) then + end_i, end_j = i, j + end + if start_i and end_i then break end + end + if start_i and end_i then break end + end + if not (start_i and end_i) then return end + + -- move + local new_start_i, new_start_j, new_end_i, new_end_j + if side == 0 then -- we move pos0 + new_end_i, new_end_j = end_i, end_j + if direction == 1 then -- move highlight to the right + if start_i == end_i and start_j == end_j then return end -- don't move start behind end + if start_j == #page_boxes[start_i] then -- last box of the line + new_start_i = start_i + 1 + new_start_j = 1 + table.remove(pboxes, 1) + else + new_start_i = start_i + new_start_j = start_j + 1 + pboxes[1].x = page_boxes[new_start_i][new_start_j].x0 + local last_box_j = new_start_i == new_end_i and new_end_j or #page_boxes[new_start_i] + local last_box = page_boxes[new_start_i][last_box_j] -- last highlighted box of the line + pboxes[1].w = last_box.x1 - pboxes[1].x + end + local removed_word = page_boxes[start_i][start_j].word + if removed_word then + highlight.text = highlight.text:sub(#removed_word + 2) -- remove first word and space after it + end + else -- move highlight to the left + local new_box + if start_j == 1 then -- first box of the line + if start_i == 1 then return end -- first line of the page, don't move to the previous page + new_start_i = start_i - 1 + new_start_j = #page_boxes[new_start_i] + new_box = page_boxes[new_start_i][new_start_j] + table.insert(pboxes, 1, { x = new_box.x0, y = new_box.y0, w = new_box.x1 - new_box.x0, h = new_box.y1 - new_box.y0 }) + else + new_start_i = start_i + new_start_j = start_j - 1 + new_box = page_boxes[new_start_i][new_start_j] + pboxes[1].x = new_box.x0 + local last_box_j = new_start_i == new_end_i and new_end_j or #page_boxes[new_start_i] + local last_box = page_boxes[new_start_i][last_box_j] -- last highlighted box of the line + pboxes[1].w = last_box.x1 - pboxes[1].x + end + if new_box.word then + highlight.text = new_box.word .. " " .. highlight.text + end + end + else -- we move pos1 + new_start_i, new_start_j = start_i, start_j + if direction == 1 then -- move highlight to the right + local new_box + if end_j == #page_boxes[end_i] then -- last box of the line + if end_i == #page_boxes then return end -- last line of the page, don't move to the next page + new_end_i = end_i + 1 + new_end_j = 1 + new_box = page_boxes[new_end_i][new_end_j] + table.insert(pboxes, { x = new_box.x0, y = new_box.y0, w = new_box.x1 - new_box.x0, h = new_box.y1 - new_box.y0 }) + else + new_end_i = end_i + new_end_j = end_j + 1 + new_box = page_boxes[new_end_i][new_end_j] + pboxes[#pboxes].w = new_box.x1 - pboxes[#pboxes].x + end + if new_box.word then + highlight.text = highlight.text .. " " .. new_box.word + end + else -- move highlight to the left + if start_i == end_i and start_j == end_j then return end -- don't move end before start + if end_j == 1 then -- first box of the line + new_end_i = end_i - 1 + new_end_j = #page_boxes[new_end_i] + table.remove(pboxes) + else + new_end_i = end_i + new_end_j = end_j - 1 + local last_box = page_boxes[new_end_i][new_end_j] -- last highlighted box of the line + pboxes[#pboxes].w = last_box.x1 - pboxes[#pboxes].x + end + local removed_word = page_boxes[end_i][end_j].word + if removed_word then + highlight.text = highlight.text:sub(1, -(#removed_word + 2)) -- remove last word and space before it + end + end + end + start_box, end_box = page_boxes[new_start_i][new_start_j], page_boxes[new_end_i][new_end_j] + if highlight.ext then -- multipage highlight + if side == 0 then -- we move pos0 + highlight.pos0.x = (start_box.x0 + start_box.x1) / 2 + highlight.pos0.y = (start_box.y0 + start_box.y1) / 2 + highlight.ext[page].pos0.x = highlight.pos0.x + highlight.ext[page].pos0.y = highlight.pos0.y + else + highlight.pos1.x = (end_box.x0 + end_box.x1) / 2 + highlight.pos1.y = (end_box.y0 + end_box.y1) / 2 + highlight.ext[page].pos1.x = highlight.pos1.x + highlight.ext[page].pos1.y = highlight.pos1.y + end + else + -- pos0 and pos1 may be not in order, reassign all + highlight.pos0.x = (start_box.x0 + start_box.x1) / 2 + highlight.pos0.y = (start_box.y0 + start_box.y1) / 2 + highlight.pos1.x = (end_box.x0 + end_box.x1) / 2 + highlight.pos1.y = (end_box.y0 + end_box.y1) / 2 + end + return true end function ReaderHighlight:showChooseHighlightDialog(highlights) @@ -1197,6 +1348,13 @@ end function ReaderHighlight:showHighlightDialog(index) local item = self.ui.annotation.annotations[index] + local change_boundaries_enabled = not item.text_edited + local start_prev, start_next, end_prev, end_next = "◁▒▒", "▷▒▒", "▒▒◁", "▒▒▷" + if BD.mirroredUILayout() then + -- BiDi will mirror the arrows, and this just works + start_prev, start_next = start_next, start_prev + end_prev, end_next = end_next, end_prev + end local edit_highlight_dialog local buttons = { { @@ -1245,23 +1403,10 @@ function ReaderHighlight:showHighlightDialog(index) end, }, }, - } - - if self.ui.rolling then - local enabled = not item.text_edited - local start_prev = "◁▒▒" - local start_next = "▷▒▒" - local end_prev = "▒▒◁" - local end_next = "▒▒▷" - if BD.mirroredUILayout() then - -- BiDi will mirror the arrows, and this just works - start_prev, start_next = start_next, start_prev - end_prev, end_next = end_next, end_prev - end - table.insert(buttons, { + { { text = start_prev, - enabled = enabled, + enabled = change_boundaries_enabled, callback = function() self:updateHighlight(index, 0, -1, false) end, @@ -1271,7 +1416,7 @@ function ReaderHighlight:showHighlightDialog(index) }, { text = start_next, - enabled = enabled, + enabled = change_boundaries_enabled, callback = function() self:updateHighlight(index, 0, 1, false) end, @@ -1281,7 +1426,7 @@ function ReaderHighlight:showHighlightDialog(index) }, { text = end_prev, - enabled = enabled, + enabled = change_boundaries_enabled, callback = function() self:updateHighlight(index, 1, -1, false) end, @@ -1291,16 +1436,16 @@ function ReaderHighlight:showHighlightDialog(index) }, { text = end_next, - enabled = enabled, + enabled = change_boundaries_enabled, callback = function() self:updateHighlight(index, 1, 1, false) end, hold_callback = function() self:updateHighlight(index, 1, 1, true) end, - } - }) - end + }, + }, + } edit_highlight_dialog = ButtonDialog:new{ name = "edit_highlight_dialog", -- for unit tests buttons = buttons, @@ -1522,10 +1667,23 @@ function ReaderHighlight:onHold(arg, ges) -- use text selections throughout readerhighlight in order to allow the -- highlight to be corrected by language-specific plugins more easily. self.is_word_selection = true + local pos = word.pos self.selected_text = { text = word.word or "", - pos0 = word.pos0 or word.pos, - pos1 = word.pos1 or word.pos, + pos0 = word.pos0 or { + page = pos.page, + rotation = pos.rotation, + x = pos.x, + y = pos.y, + zoom = pos.zoom, + }, + pos1 = word.pos1 or { + page = pos.page, + rotation = pos.rotation, + x = pos.x, + y = pos.y, + zoom = pos.zoom, + }, sboxes = word.sbox and { word.sbox }, pboxes = word.pbox and { word.pbox }, }