From a1aa41136c89c4ef416e4c01cb998efb4cc9560c Mon Sep 17 00:00:00 2001 From: chrox Date: Sun, 3 Feb 2013 04:42:59 +0800 Subject: [PATCH] add manual page crop for pdf/djvu documents --- frontend/document/credocument.lua | 2 +- frontend/document/document.lua | 55 +++++-- frontend/document/koptinterface.lua | 15 +- frontend/ui/config.lua | 56 ++++++-- frontend/ui/geometry.lua | 8 +- frontend/ui/reader/readerconfig.lua | 25 +--- frontend/ui/reader/readercropping.lua | 198 ++++++++++++++++++++++++++ frontend/ui/reader/readerview.lua | 2 +- frontend/ui/reader/readerzooming.lua | 5 +- frontend/ui/readerui.lua | 9 ++ 10 files changed, 328 insertions(+), 47 deletions(-) create mode 100644 frontend/ui/reader/readercropping.lua diff --git a/frontend/document/credocument.lua b/frontend/document/credocument.lua index f1d2f469c..f30f333c9 100644 --- a/frontend/document/credocument.lua +++ b/frontend/document/credocument.lua @@ -26,7 +26,7 @@ CreOptions = { item_align_center = 1.0, spacing = Screen:getWidth()*0.03, item_font_size = {18, 20, 22, 24, 29, 33, 39, 44}, - values = {18, 20, 22, 24, 29, 33, 39, 44}, + args = {18, 20, 22, 24, 29, 33, 39, 44}, default_value = 1, event = "SetFontSize", }, diff --git a/frontend/document/document.lua b/frontend/document/document.lua index a834d74fe..f453bda95 100644 --- a/frontend/document/document.lua +++ b/frontend/document/document.lua @@ -47,13 +47,16 @@ Document = { number_of_pages = 0, -- if not pageable, length of the document in pixels doc_height = 0, - + -- other metadata title = "", author = "", date = "" }, - + + -- override bbox from orignal page's getUsedBBox + bbox = {}, + -- flag to show whether the document was opened successfully is_open = false, error_message = nil, @@ -123,20 +126,56 @@ function Document:getPageDimensions(pageno, zoom, rotation) return native_dimen end +function Document:oddEven(number) + if number % 2 == 1 then + return "odd" + else + return "even" + end +end + +function Document:getPageBBox(pageno) + local bbox = self.bbox[pageno] -- exact + local oddEven = self:oddEven(pageno) + if bbox ~= nil then + DEBUG("bbox from", pageno) + else + bbox = self.bbox[oddEven] -- odd/even + end + if bbox ~= nil then -- last used up to this page + DEBUG("bbox from", oddEven) + else + for i = 0,pageno do + bbox = self.bbox[ pageno - i ] + if bbox ~= nil then + DEBUG("bbox from", pageno - i) + break + end + end + end + if bbox == nil then -- fallback bbox + bbox = self:getUsedBBox(pageno) + DEBUG("bbox from ORIGINAL page") + end + DEBUG("final bbox", bbox) + return bbox +end + --[[ This method returns pagesize if bbox is corrupted --]] function Document:getUsedBBoxDimensions(pageno, zoom, rotation) - ubbox = self:getUsedBBox(pageno) - if ubbox.x0 < 0 or ubbox.y0 < 0 or ubbox.x1 < 0 or ubbox.y1 < 0 then + local bbox = self:getPageBBox(pageno) + local ubbox_dimen = nil + if bbox.x0 < 0 or bbox.y0 < 0 or bbox.x1 < 0 or bbox.y1 < 0 then -- if document's bbox info is corrupted, we use the page size ubbox_dimen = self:getPageDimensions(pageno, zoom, rotation) else ubbox_dimen = Geom:new{ - x = ubbox.x0, - y = ubbox.y0, - w = ubbox.x1 - ubbox.x0, - h = ubbox.y1 - ubbox.y0, + x = bbox.x0, + y = bbox.y0, + w = bbox.x1 - bbox.x0, + h = bbox.y1 - bbox.y0, } if zoom ~= 1 then ubbox_dimen:transformByScale(zoom) diff --git a/frontend/document/koptinterface.lua b/frontend/document/koptinterface.lua index a98fb354d..9f12e7530 100644 --- a/frontend/document/koptinterface.lua +++ b/frontend/document/koptinterface.lua @@ -39,8 +39,11 @@ KoptOptions = { name="trim_page", name_text = "Page Crop", toggle = {"auto", "manual"}, + alternate = false, values = {1, 0}, default_value = 1, + event = "PageCrop", + args = {"auto", "manual"}, } } }, @@ -115,7 +118,15 @@ KoptOptions = { toggle = {"On", "Off"}, values = {1, 0}, default_value = 0, - event = "RedrawCurrentPage", + events = { + { + event = "RedrawCurrentPage", + }, + { + event = "SetZoomMode", + args = {"page", nil}, + }, + } }, { name="screen_rotation", @@ -185,7 +196,7 @@ function KoptInterface:getKOPTContext(doc, pageno) kc:setDefectSize(doc.configurable.defect_size) kc:setLineSpacing(doc.configurable.line_spacing) kc:setWordSpacing(doc.configurable.word_spacing) - local bbox = doc:getUsedBBox(pageno) + local bbox = doc:getPageBBox(pageno) kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1) return kc end diff --git a/frontend/ui/config.lua b/frontend/ui/config.lua index c8f797dcb..aaae3f12b 100644 --- a/frontend/ui/config.lua +++ b/frontend/ui/config.lua @@ -88,12 +88,22 @@ function OptionTextItem:onTapSelect() self[1].color = 15 local option_value = nil local option_arg = nil - if type(self.values) == "table" then + if self.values then + self.values = self.values or {} option_value = self.values[self.current_item] - self.config:onConfigChoice(self.name, option_value, self.event) - elseif type(self.args) == "table" then + self.config:onConfigChoice(self.name, option_value) + end + if self.event then + self.args = self.args or {} option_arg = self.args[self.current_item] - self.config:onConfigChoice(self.name, option_arg, self.event) + self.config:onConfigEvent(self.event, option_arg) + end + if self.events then + for i=0,#self.events do + self.events[i].args = self.events[i].args or {} + option_arg = self.events[i].args[self.current_item] + self.config:onConfigEvent(self.events[i].event, option_arg) + end end UIManager.repaint_all = true return true @@ -234,7 +244,7 @@ function ToggleSwitch:setPosition(position) end function ToggleSwitch:togglePosition(position) - if self.n_pos == 2 then + if self.n_pos == 2 and self.alternate ~= false then self.position = (self.position+1)%self.n_pos self.position = self.position == 0 and self.n_pos or self.position else @@ -248,12 +258,22 @@ function ToggleSwitch:onTapSelect(position) self:togglePosition(position) local option_value = nil local option_arg = nil - if type(self.values) == "table" then + if self.values then + self.values = self.values or {} option_value = self.values[self.position] - self.config:onConfigChoice(self.name, option_value, self.event) - elseif type(self.args) == "table" then + self.config:onConfigChoice(self.name, option_value) + end + if self.event then + self.args = self.args or {} option_arg = self.args[self.position] - self.config:onConfigChoice(self.name, option_arg, self.event) + self.config:onConfigEvent(self.event, option_arg) + end + if self.events then + for i=1,#self.events do + self.events[i].args = self.events[i].args or {} + option_arg = self.events[i].args[self.position] + self.config:onConfigEvent(self.events[i].event, option_arg) + end end UIManager.repaint_all = true end @@ -319,6 +339,7 @@ function ConfigOption:init() if self.options[c].name then if self.options[c].values then local val = self.config.configurable[self.options[c].name] + DEBUG("val", val) local min_diff = math.abs(val - self.options[c].values[1]) local diff = nil for index, val_ in pairs(self.options[c].values) do @@ -384,9 +405,11 @@ function ConfigOption:init() local switch = ToggleSwitch:new{ name = self.options[c].name, toggle = self.options[c].toggle, + alternate = self.options[c].alternate, values = self.options[c].values, args = self.options[c].args, event = self.options[c].event, + events = self.options[c].events, config = self.config, } local position = current_item @@ -552,7 +575,18 @@ function ConfigDialog:onShowConfigPanel(index) return true end -function ConfigDialog:onCloseMenu() +function ConfigDialog:onConfigChoice(option_name, option_value) + DEBUG("config option value", option_name, option_value) + self.configurable[option_name] = option_value +end + +function ConfigDialog:onConfigEvent(option_event, option_arg) + DEBUG("config option event", option_event, option_arg) + self.ui:handleEvent(Event:new(option_event, option_arg)) +end + +function ConfigDialog:closeDialog() + DEBUG("closing config dialog") UIManager:close(self) if self.close_callback then self.close_callback() @@ -562,7 +596,7 @@ end function ConfigDialog:onTapCloseMenu(arg, ges_ev) if ges_ev.pos:notIntersectWith(self.dialog_dimen) then - self:onCloseMenu() + self:closeDialog() return true end end diff --git a/frontend/ui/geometry.lua b/frontend/ui/geometry.lua index 3eefdbd75..497100c6e 100644 --- a/frontend/ui/geometry.lua +++ b/frontend/ui/geometry.lua @@ -259,8 +259,12 @@ function Geom:offsetWithin(rect_b, dx, dy) end end - - +--[[ +return the Euclidean distance between two geoms +]]-- +function Geom:distance(geom) + return math.sqrt(math.pow(self.x - geom.x, 2) + math.pow(self.y - geom.y, 2)) +end --[[ Simple math helper function diff --git a/frontend/ui/reader/readerconfig.lua b/frontend/ui/reader/readerconfig.lua index 2eed6226f..6d536a60d 100644 --- a/frontend/ui/reader/readerconfig.lua +++ b/frontend/ui/reader/readerconfig.lua @@ -72,31 +72,14 @@ function ReaderConfig:init() end function ReaderConfig:onShowConfigMenu() - local config_dialog = ConfigDialog:new{ + self.config_dialog = ConfigDialog:new{ dimen = self.dimen:copy(), ui = self.ui, configurable = self.configurable, config_options = self.options, } - function config_dialog:onConfigChoice(option_name, option_value, event) - self.configurable[option_name] = option_value - if event then - self.ui:handleEvent(Event:new(event, option_value)) - end - end - - local dialog_container = CenterContainer:new{ - config_dialog, - dimen = self.dimen:copy(), - } - config_dialog.close_callback = function () - UIManager:close(menu_container) - end - - self.dialog_container = dialog_container - - UIManager:show(config_dialog) + UIManager:show(self.config_dialog) return true end @@ -111,6 +94,10 @@ function ReaderConfig:onSetDimensions(dimen) self:init() end +function ReaderConfig:onCloseConfig() + self.config_dialog:closeDialog() +end + function ReaderConfig:onReadSettings(config) self.configurable:loadSettings(config, self.options.prefix..'_') end diff --git a/frontend/ui/reader/readercropping.lua b/frontend/ui/reader/readercropping.lua new file mode 100644 index 000000000..66d49b2f0 --- /dev/null +++ b/frontend/ui/reader/readercropping.lua @@ -0,0 +1,198 @@ +--[[ +BBoxWidget shows a bbox for page cropping +]] +BBoxWidget = InputContainer:new{ + page_bbox = nil, + screen_bbox = nil, + linesize = 2, +} + +function BBoxWidget:getSize() + return Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight() + } +end + +function BBoxWidget:paintTo(bb, x, y) + self.screen_bbox = self.screen_bbox or self:page_to_screen() + local bbox = self.screen_bbox + -- upper_left + bb:invertRect(bbox.x0 + self.linesize, bbox.y0, bbox.x1 - bbox.x0, self.linesize) + bb:invertRect(bbox.x0, bbox.y0, self.linesize, bbox.y1 - bbox.y0 + self.linesize) + -- bottom_right + bb:invertRect(bbox.x0 + self.linesize, bbox.y1, bbox.x1 - bbox.x0 - self.linesize, self.linesize) + bb:invertRect(bbox.x1, bbox.y0 + self.linesize, self.linesize, bbox.y1 - bbox.y0) +end + +-- transform page bbox to screen bbox +function BBoxWidget:page_to_screen() + local bbox = {} + local scale = self.crop.zoom + local screen_offset = self.crop.screen_offset + DEBUG("screen offset in page_to_screen", screen_offset) + bbox.x0 = self.page_bbox.x0 * scale + screen_offset.x + bbox.y0 = self.page_bbox.y0 * scale + screen_offset.y + bbox.x1 = self.page_bbox.x1 * scale + screen_offset.x + bbox.y1 = self.page_bbox.y1 * scale + screen_offset.y + return bbox +end + +-- transform screen bbox to page bbox +function BBoxWidget:screen_to_page() + local bbox = {} + local scale = self.crop.zoom + local screen_offset = self.crop.screen_offset + DEBUG("screen offset in screen_to_page", screen_offset) + bbox.x0 = self.screen_bbox.x0 / scale - screen_offset.x + bbox.y0 = self.screen_bbox.y0 / scale - screen_offset.y + bbox.x1 = self.screen_bbox.x1 / scale - screen_offset.x + bbox.y1 = self.screen_bbox.y1 / scale - screen_offset.y + return bbox +end + +function BBoxWidget:oddEven(number) + if number % 2 == 1 then + return "odd" + else + return "even" + end +end + +function BBoxWidget:init() + if Device:isTouchDevice() then + self.ges_events = { + AdjustCrop = { + GestureRange:new{ + ges = "tap", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight() + } + } + }, + ConfirmCrop = { + GestureRange:new{ + ges = "double_tap", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight() + } + } + }, + CancelCrop = { + GestureRange:new{ + ges = "hold_release", + range = Geom:new{ + x = 0, y = 0, + w = Screen:getWidth(), + h = Screen:getHeight() + } + } + }, + } + end +end + +function BBoxWidget:onGesture(ev) + for name, gsseq in pairs(self.ges_events) do + for _, gs_range in ipairs(gsseq) do + --DEBUG("gs_range", gs_range) + if gs_range:match(ev) then + local eventname = gsseq.event or name + return self:handleEvent(Event:new(eventname, ev.pos)) + end + end + end +end + +function BBoxWidget:onAdjustCrop(pos) + DEBUG("adjusting crop bbox with pos", pos) + local bbox = self.screen_bbox + local upper_left = Geom:new{ x = bbox.x0, y = bbox.y0} + local bottom_right = Geom:new{ x = bbox.x1, y = bbox.y1} + if upper_left:distance(pos) < bottom_right:distance(pos) then + upper_left.x = pos.x + upper_left.y = pos.y + else + bottom_right.x = pos.x + bottom_right.y = pos.y + end + self.screen_bbox = { + x0 = upper_left.x, + y0 = upper_left.y, + x1 = bottom_right.x, + y1 = bottom_right.y + } + UIManager.repaint_all = true +end + +function BBoxWidget:onConfirmCrop() + self.page_bbox = self:screen_to_page() + --DEBUG("new bbox", self.page_bbox) + UIManager:close(self) + self.ui:handleEvent(Event:new("BBoxUpdate"), self.page_bbox) + self.document.bbox[self.pageno] = self.page_bbox + self.document.bbox[self:oddEven(self.pageno)] = self.page_bbox + self.ui:handleEvent(Event:new("SetZoomMode", self.orig_zoom_mode)) + self.document.configurable.text_wrap = self.orig_reflow_mode + UIManager.repaint_all = true +end + +ReaderCropping = InputContainer:new{} + +function ReaderCropping:onPageCrop(mode) + if mode == "auto" then return end + local orig_reflow_mode = self.document.configurable.text_wrap + self.document.configurable.text_wrap = 0 + self.ui:handleEvent(Event:new("CloseConfig")) + self.ui:handleEvent(Event:new("SetZoomMode", "page")) + local ubbox = self.document:getPageBBox(self.current_page) + --DEBUG("used page bbox", ubbox) + self.crop_bbox = BBoxWidget:new{ + page_bbox = ubbox, + ui = self.ui, + crop = self, + document = self.document, + pageno = self.current_page, + orig_zoom_mode = self.orig_zoom_mode, + orig_reflow_mode = orig_reflow_mode, + } + UIManager:show(self.crop_bbox) + return true +end + +function ReaderCropping:onPageUpdate(page_no) + --DEBUG("page updated to", page_no) + self.current_page = page_no +end + +function ReaderCropping:onZoomUpdate(zoom) + --DEBUG("zoom updated to", zoom) + self.zoom = zoom +end + +function ReaderCropping:onScreenOffsetUpdate(screen_offset) + --DEBUG("offset updated to", screen_offset) + self.screen_offset = screen_offset +end + +function ReaderCropping:onSetZoomMode(mode) + if self.orig_zoom_mode == nil then + --DEBUG("backup zoom mode", mode) + self.orig_zoom_mode = mode + end +end + +function ReaderCropping:onReadSettings(config) + local bbox = config:readSetting("bbox") + self.document.bbox = bbox + DEBUG("read document bbox", self.document.bbox) +end + +function ReaderCropping:onCloseDocument() + self.ui.doc_settings:saveSetting("bbox", self.document.bbox) +end diff --git a/frontend/ui/reader/readerview.lua b/frontend/ui/reader/readerview.lua index 82d0e6d30..4e49d2308 100644 --- a/frontend/ui/reader/readerview.lua +++ b/frontend/ui/reader/readerview.lua @@ -38,7 +38,7 @@ function ReaderView:paintTo(bb, x, y) bb:paintRect(x, y, inner_offset.x, self.ui.dimen.h, self.outer_page_color) bb:paintRect(x + self.ui.dimen.w - inner_offset.x - 1, y, inner_offset.x + 1, self.ui.dimen.h, self.outer_page_color) end - + self.ui:handleEvent(Event:new("ScreenOffsetUpdate", inner_offset)) -- draw content if self.ui.document.info.has_pages then self.ui.document:drawPage( diff --git a/frontend/ui/reader/readerzooming.lua b/frontend/ui/reader/readerzooming.lua index ecfb7cf6d..99440a239 100644 --- a/frontend/ui/reader/readerzooming.lua +++ b/frontend/ui/reader/readerzooming.lua @@ -61,7 +61,7 @@ function ReaderZooming:onReadSettings(config) if not zoom_mode then zoom_mode = self.DEFAULT_ZOOM_MODE end - self:onSetZoomMode(zoom_mode) + self.ui:handleEvent(Event:new("SetZoomMode", zoom_mode)) end function ReaderZooming:onCloseDocument() @@ -99,7 +99,6 @@ function ReaderZooming:onSetZoomMode(new_mode) self:setZoom() self.ui:handleEvent(Event:new("ZoomModeUpdate", new_mode)) end - return true end function ReaderZooming:onPageUpdate(new_page_no) @@ -148,7 +147,7 @@ function ReaderZooming:setZoom() elseif self.zoom_mode == "contentheight" or self.zoom_mode == "pageheight" then self.zoom = zoom_h end - self.view:onZoomUpdate(self.zoom) + self.ui:handleEvent(Event:new("ZoomUpdate", self.zoom)) end function ReaderZooming:genSetZoomModeCallBack(mode) diff --git a/frontend/ui/readerui.lua b/frontend/ui/readerui.lua index a4331e97b..52177e84e 100644 --- a/frontend/ui/readerui.lua +++ b/frontend/ui/readerui.lua @@ -11,6 +11,7 @@ require "ui/reader/readerfont" require "ui/reader/readertypeset" require "ui/reader/readermenu" require "ui/reader/readerconfig" +require "ui/reader/readercropping" --[[ This is an abstraction for a reader interface @@ -112,6 +113,14 @@ function ReaderUI:init() ui = self } table.insert(self, panner) + -- cropping controller + local cropper = ReaderCropping:new{ + dialog = self.dialog, + view = self[1], + ui = self, + document = self.document, + } + table.insert(self, cropper) else -- make sure we load document first before calling any callback table.insert(self.postInitCallback, function()