diff --git a/frontend/apps/reader/modules/readerhighlight.lua b/frontend/apps/reader/modules/readerhighlight.lua index 3cfe1607a..455ed40f4 100644 --- a/frontend/apps/reader/modules/readerhighlight.lua +++ b/frontend/apps/reader/modules/readerhighlight.lua @@ -13,7 +13,6 @@ local SpinWidget = require("ui/widget/spinwidget") local TextViewer = require("ui/widget/textviewer") local Translator = require("ui/translator") local UIManager = require("ui/uimanager") -local dbg = require("dbg") local ffiUtil = require("ffi/util") local logger = require("logger") local util = require("util") @@ -125,11 +124,11 @@ function ReaderHighlight:init() end, } end, - ["06_dictionary"] = function(this) + ["06_dictionary"] = function(this, index) return { text = _("Dictionary"), callback = function() - this:onHighlightDictLookup() + this:lookupDict(index) -- We don't call this:onClose(), same reason as above end, } @@ -1030,6 +1029,18 @@ function ReaderHighlight:onTap(_, ges) end end +function ReaderHighlight:getHighlightVisibleBoxes(index) + local boxes = {} + for _, box in ipairs(self.view.highlight.visible_boxes) do + if box.index == index then + table.insert(boxes, box.rect) + end + end + if next(boxes) ~= nil then + return boxes + end +end + function ReaderHighlight:updateHighlight(index, side, direction, move_by_char) local highlight = self.ui.annotation.annotations[index] local highlight_before = util.tableDeepCopy(highlight) @@ -1150,6 +1161,9 @@ function ReaderHighlight:showHighlightNoteOrDialog(index) text = bookmark_note, width = math.floor(math.min(self.screen_w, self.screen_h) * 0.8), height = math.floor(math.max(self.screen_w, self.screen_h) * 0.4), + anchor = function() + return self:_getDialogAnchor(textviewer, index) + end, buttons_table = { { { @@ -1314,7 +1328,7 @@ function ReaderHighlight:onShowHighlightDialog(index) self.edit_highlight_dialog = ButtonDialog:new{ -- in self for unit tests buttons = buttons, anchor = function() - return self:_getDialogAnchor(self.edit_highlight_dialog, item) + return self:_getDialogAnchor(self.edit_highlight_dialog, index) end, } UIManager:show(self.edit_highlight_dialog) @@ -1355,7 +1369,7 @@ function ReaderHighlight:onShowHighlightMenu(index) self.highlight_dialog = ButtonDialog:new{ buttons = highlight_buttons, anchor = function() - return self:_getDialogAnchor(self.highlight_dialog, not self.gest_pos and self.ui.annotation.annotations[index]) + return self:_getDialogAnchor(self.highlight_dialog, index) end, tap_close_callback = function() self:handleEvent(Event:new("Tap")) end, } @@ -1363,55 +1377,42 @@ function ReaderHighlight:onShowHighlightMenu(index) -- or the buggy Sage kernel may alpha-blend it into the page (with a bogus alpha value, to boot)... UIManager:show(self.highlight_dialog, "[ui]") end -dbg:guard(ReaderHighlight, "onShowHighlightMenu", - function(self) - assert(self.selected_text ~= nil, - "onShowHighlightMenu must not be called with nil self.selected_text!") - end) -function ReaderHighlight:_getDialogAnchor(dialog, item) +function ReaderHighlight:_getDialogAnchor(dialog, index) local position = G_reader_settings:readSetting("highlight_dialog_position", "center") if position == "center" then return end + local padding = Size.padding.small -- vertical padding, do not stick to the highlight box or to the screen edge local dialog_box = dialog:getContentSize() local anchor_x = math.floor((self.screen_w - dialog_box.w) / 2) -- center by width local anchor_y, prefers_pop_down if position == "top" then - anchor_y = Size.padding.small -- do not stick to the edge + anchor_y = padding prefers_pop_down = true elseif position == "bottom" then - anchor_y = self.screen_h - Size.padding.small + anchor_y = self.screen_h - padding else -- "gesture" - local pos0, pos1 - if item then -- highlight - if self.ui.rolling then - local y, x = self.ui.document:getScreenPositionFromXPointer(item.pos0) - pos0 = x ~= nil and y ~= nil and { x = x, y = y } or nil - y, x = self.ui.document:getScreenPositionFromXPointer(item.pos1) - pos1 = x ~= nil and y ~= nil and { x = x, y = y } or nil - else - pos0, pos1 = item.pos0, item.pos1 - end - else -- gesture - pos0, pos1 = unpack(self.gest_pos) - self.gest_pos = nil + local boxes = index and self:getHighlightVisibleBoxes(index) or (self.selected_text.sboxes or self.selected_text.pboxes) + if boxes == nil then return end -- fallback to "center" + local box0, box1 = boxes[1], boxes[#boxes] + if box0.y > box1.y then + box0, box1 = box1, box0 end - if pos0 == nil or pos1 == nil then return end -- fallback to "center" - if pos0.y > pos1.y then -- try to show the dialog below the highlight - pos1 = pos0 + if self.ui.paging then + local page = index and self.ui.annotation.annotations[index].pos0.page or self.selected_text.pos0.page + box0 = self.view:pageToScreenTransform(page, box0) + box1 = self.view:pageToScreenTransform(page, box1) + if box0 == nil or box1 == nil then return end end - local text_box = self.ui.document:getWordFromPosition(pos1, true) - if text_box then - text_box = text_box.sbox - if text_box and self.ui.paging then - text_box = self.view:pageToScreenTransform(self.ui.paging.current_page, text_box) - end - end - if text_box == nil then return end -- fallback to "center" - anchor_y = text_box.y + text_box.h + Size.padding.small -- do not stick to the box - if anchor_y + dialog_box.h <= self.screen_h - Size.padding.small then -- enough room below box with gest_pos + local y0 = box0.y + local y1 = box1.y + box1.h + local dialog_box_h = dialog_box.h + 2 * padding + if y1 + dialog_box_h <= self.screen_h then -- below highlight, preferable + anchor_y = y1 + padding prefers_pop_down = true - else -- above box with gest_pos - anchor_y = text_box.y - Size.padding.small + elseif dialog_box_h <= y0 then -- above highlight + anchor_y = y0 - padding + else -- not enough room below and above, fallback to "center" + return end end return { x = anchor_x, y = anchor_y, h = 0, w = 0 }, prefers_pop_down @@ -1741,7 +1742,7 @@ function ReaderHighlight:onHoldPan(_, ges) end self:_resetHoldTimer() -- selection updated logger.dbg("selected text:", self.selected_text) - if self.selected_text then + if self.ui.paging and self.selected_text then self.view.highlight.temp[self.hold_pos.page] = self.selected_text.sboxes end UIManager:setDirty(self.dialog, "ui") @@ -1756,36 +1757,33 @@ You can download language data files for Tesseract version 5.3.4 from https://te Copy the language data files (e.g., eng.traineddata for English and spa.traineddata for Spanish) into koreader/data/tessdata]]) -function ReaderHighlight:lookup(selected_text, selected_link) +function ReaderHighlight:lookupDictWord() -- convert sboxes to word boxes local word_boxes = {} - for i, sbox in ipairs(selected_text.sboxes) do + for i, sbox in ipairs(self.selected_text.sboxes) do word_boxes[i] = self.view:pageToScreenTransform(self.hold_pos.page, sbox) end - -- if we extracted text directly - if #selected_text.text > 0 and self.hold_pos then - self.ui:handleEvent(Event:new("LookupWord", selected_text.text, false, word_boxes, self, selected_link)) + if #self.selected_text.text > 0 then + self.ui.dictionary:onLookupWord(self.selected_text.text, false, word_boxes, self, self.selected_link) -- or we will do OCR - elseif selected_text.sboxes and self.hold_pos then - local text = self.ui.document:getOCRText(self.hold_pos.page, selected_text.sboxes) + elseif self.selected_text.sboxes then + local text = self.ui.document:getOCRText(self.hold_pos.page, self.selected_text.sboxes) if not text then -- getOCRText is not implemented in some document backends, but -- getOCRWord is implemented everywhere. As such, fall back to -- getOCRWord. text = "" - for _, sbox in ipairs(selected_text.sboxes) do + for _, sbox in ipairs(self.selected_text.sboxes) do local word = self.ui.document:getOCRWord(self.hold_pos.page, { sbox = sbox }) logger.dbg("OCRed word:", word) --- @fixme This might produce incorrect results on RTL text. - if word and word ~= "" then - text = text .. word - end + text = text .. (word or "") end end logger.dbg("OCRed text:", text) if text and text ~= "" then - self.ui:handleEvent(Event:new("LookupWord", text, false, word_boxes, self, selected_link)) + self.ui.dictionary:onLookupWord(text, false, word_boxes, self, self.selected_link) else UIManager:show(InfoMessage:new{ text = info_message_ocr_text, @@ -1793,11 +1791,6 @@ function ReaderHighlight:lookup(selected_text, selected_link) end end end -dbg:guard(ReaderHighlight, "lookup", - function(self, selected_text, selected_link) - assert(selected_text ~= nil, - "lookup must not be called with nil selected_text!") - end) function ReaderHighlight:getSelectedWordContext(nb_words) if not self.selected_text then return end @@ -1908,7 +1901,7 @@ function ReaderHighlight:onHoldRelease() if self.selected_text then if self.is_word_selection then - self:lookup(self.selected_text, self.selected_link) + self:lookupDictWord() else if long_final_hold or default_highlight_action == "ask" then -- bypass default action and show popup if long final hold @@ -1928,7 +1921,7 @@ function ReaderHighlight:onHoldRelease() self:lookupWikipedia() self:onClose() elseif default_highlight_action == "dictionary" then - self:onHighlightDictLookup() + self:lookupDict() self:onClose() elseif default_highlight_action == "search" then self:onHighlightSearch() @@ -2110,11 +2103,19 @@ function ReaderHighlight:onHighlightSearch() end end -function ReaderHighlight:onHighlightDictLookup() +function ReaderHighlight:lookupDict(index) logger.dbg("dictionary lookup highlight") self:highlightFromHoldPos() if self.selected_text then - self.ui:handleEvent(Event:new("LookupWord", util.cleanupSelectedText(self.selected_text.text))) + local boxes = index and self:getHighlightVisibleBoxes(index) or (self.selected_text.sboxes or self.selected_text.pboxes) + local word_boxes + if boxes ~= nil then + word_boxes = {} + for i, box in ipairs(boxes) do + word_boxes[i] = self.view:pageToScreenTransform(self.selected_text.pos0.page, box) + end + end + self.ui.dictionary:onLookupWord(util.cleanupSelectedText(self.selected_text.text), false, word_boxes) end end @@ -2295,6 +2296,7 @@ function ReaderHighlight:extendSelection() -- 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 + new_pboxes = self.document:getScreenBoxesFromPositions(new_pos0, new_pos1) -- true to draw new_text = self.ui.document:getTextFromXPointers(new_pos0, new_pos1, true) end diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index e490e5c72..0219eae29 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -713,12 +713,12 @@ function CreDocument:getTextFromPositions(pos0, pos1, do_not_draw_selection) drawSelection, drawSegmentedSelection) logger.dbg("CreDocument: get text range", text_range) if text_range then - -- local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1) + local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1) return { text = text_range.text, pos0 = text_range.pos0, pos1 = text_range.pos1, - --sboxes = line_boxes, -- boxes on screen + sboxes = line_boxes, -- boxes on screen } end end diff --git a/frontend/ui/widget/textviewer.lua b/frontend/ui/widget/textviewer.lua index c700baee5..b76cc889c 100644 --- a/frontend/ui/widget/textviewer.lua +++ b/frontend/ui/widget/textviewer.lua @@ -339,6 +339,7 @@ function TextViewer:init(reinit) -- a few things before forwarding them "touch", "pan", "pan_release", }, + anchor = self.anchor, self.frame, } self[1] = WidgetContainer:new{ @@ -348,6 +349,10 @@ function TextViewer:init(reinit) } end +function TextViewer:getContentSize() + return self.movable.dimen +end + function TextViewer:onCloseWidget() UIManager:setDirty(nil, function() return "partial", self.frame.dimen