diff --git a/frontend/document/djvudocument.lua b/frontend/document/djvudocument.lua index d8f627e06..1b2a2b55c 100644 --- a/frontend/document/djvudocument.lua +++ b/frontend/document/djvudocument.lua @@ -1,14 +1,23 @@ require "cache" require "ui/geometry" +require "ui/screen" +require "ui/device" +require "ui/reader/readerconfig" +require "document/koptinterface" DjvuDocument = Document:new{ _document = false, -- libdjvulibre manages its own additional cache, default value is hard written in c module. djvulibre_cache_size = nil, - dc_null = DrawContext.new() + dc_null = DrawContext.new(), + screen_size = Screen:getSize(), + screen_dpi = Device:getModel() == "KindlePaperWhite" and 212 or 167, + configurable = Configurable, + koptinterface = KoptInterface, } function DjvuDocument:init() + self.configurable:loadDefaults() if not validDjvuFile(self.file) then self.error_message = "Not a valid DjVu file" return @@ -22,6 +31,7 @@ function DjvuDocument:init() end self.is_open = true self.info.has_pages = true + self.info.configurable = true self:_readMetadata() end @@ -38,7 +48,8 @@ end function DjvuDocument:getUsedBBox(pageno) -- djvu does not support usedbbox, so fake it. local used = {} - used.x, used.y, used.w, used.h = 0.01, 0.01, -0.01, -0.01 + local native_dim = self:getNativePageDimensions(pageno) + used.x0, used.y0, used.x1, used.y1 = 0, 0, native_dim.w, native_dim.h return used end @@ -52,4 +63,28 @@ function DjvuDocument:invertTextYAxel(pageno, text_table) return text_table end +function DjvuDocument:getPageDimensions(pageno, zoom, rotation) + if self.configurable.text_wrap == 1 then + return self.koptinterface:getPageDimensions(self, pageno, zoom, rotation) + else + return Document.getPageDimensions(self, pageno, zoom, rotation) + end +end + +function DjvuDocument:renderPage(pageno, rect, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, render_mode) + else + return Document.renderPage(self, pageno, rect, zoom, rotation, render_mode) + end +end + +function DjvuDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + else + Document.drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + end +end + DocumentRegistry:addProvider("djvu", "application/djvu", DjvuDocument) diff --git a/frontend/document/document.lua b/frontend/document/document.lua index fca96135b..3482c21e1 100644 --- a/frontend/document/document.lua +++ b/frontend/document/document.lua @@ -254,5 +254,4 @@ end require "document/pdfdocument" require "document/djvudocument" -require "document/koptdocument" require "document/credocument" diff --git a/frontend/document/koptdocument.lua b/frontend/document/koptdocument.lua deleted file mode 100644 index 98a92d66e..000000000 --- a/frontend/document/koptdocument.lua +++ /dev/null @@ -1,249 +0,0 @@ -require "cache" -require "ui/geometry" -require "ui/screen" -require "ui/device" -require "ui/reader/readerconfig" - -Configurable = {} - -function Configurable:hash(sep) - local hash = "" - local excluded = {multi_threads = true,} - for key,value in pairs(self) do - if type(value) == "number" and not excluded[key] then - hash = hash..sep..value - end - end - return hash -end - -function Configurable:loadDefaults() - for i=1,#KOPTOptions do - local options = KOPTOptions[i].options - for j=1,#KOPTOptions[i].options do - local key = KOPTOptions[i].options[j].name - self[key] = KOPTOptions[i].options[j].default_value - end - end -end - -function Configurable:loadSettings(settings, prefix) - for key,value in pairs(self) do - if type(value) == "number" then - saved_value = settings:readSetting(prefix..key) - self[key] = (saved_value == nil) and self[key] or saved_value - --Debug("Configurable:loadSettings", "key", key, "saved value", saved_value,"Configurable.key", self[key]) - end - end - --Debug("loaded config:", dump(Configurable)) -end - -function Configurable:saveSettings(settings, prefix) - for key,value in pairs(self) do - if type(value) == "number" then - settings:saveSetting(prefix..key, value) - end - end -end - --- Any document processed by K2pdfopt is called a koptdocument -KoptDocument = Document:new{ - _document = false, - -- muPDF manages its own additional cache - mupdf_cache_size = 5 * 1024 * 1024, - djvulibre_cache_size = nil, - dc_null = DrawContext.new(), - screen_size = Screen:getSize(), - screen_dpi = Device:getModel() == "KindlePaperWhite" and 212 or 167, - configurable = Configurable, -} - -function KoptDocument:init() - self.configurable:loadDefaults() - self.file_type = string.lower(string.match(self.file, ".+%.([^.]+)") or "") - if self.file_type == "pdf" then - local ok - ok, self._document = pcall(pdf.openDocument, self.file, self.mupdf_cache_size) - if not ok then - self.error_message = self.doc -- will contain error message - return - end - self.is_open = true - self.info.has_pages = true - self.info.configurable = true - if self._document:needsPassword() then - self.is_locked = true - else - self:_readMetadata() - end - - elseif self.file_type == "djvu" then - if not validDjvuFile(self.file) then - self.error_message = "Not a valid DjVu file" - return - end - - local ok - ok, self._document = pcall(djvu.openDocument, self.file, self.djvulibre_cache_size) - if not ok then - self.error_message = self.doc -- will contain error message - return - end - self.is_open = true - self.info.has_pages = true - self.info.configurable = true - self:_readMetadata() - end -end - -function KoptDocument:unlock(password) - if not self._document:authenticatePassword(password) then - self._document:close() - return false, "wrong password" - end - self.is_locked = false - return self:_readMetadata() -end - --- check DjVu magic string to validate -function validDjvuFile(filename) - f = io.open(filename, "r") - if not f then return false end - local magic = f:read(8) - f:close() - if not magic or magic ~= "AT&TFORM" then return false end - return true -end - -function KoptDocument:getUsedBBox(pageno) - if self.file_type == "pdf" then - local hash = "pgubbox|"..self.file.."|"..pageno - local cached = Cache:check(hash) - if cached then - return cached.ubbox - end - local page = self._document:openPage(pageno) - local used = {} - used.x0, used.y0, used.x1, used.y1 = page:getUsedBBox() - local pwidth, pheight = page:getSize(self.dc_null) - if used.x1 == 0 then used.x1 = pwidth end - if used.y1 == 0 then used.y1 = pheight end - -- clamp to page BBox - if used.x0 < 0 then used.x0 = 0 end; - if used.y0 < 0 then used.y0 = 0 end; - if used.x1 > pwidth then used.x1 = pwidth end - if used.y1 > pheight then used.y1 = pheight end - --@TODO give size for cacheitem? 02.12 2012 (houqp) - Cache:insert(hash, CacheItem:new{ - ubbox = used, - }) - page:close() - DEBUG("UsedBBox", used) - return used - elseif self.file_type == "djvu" then - -- djvu does not support usedbbox, so fake it. - local used = {} - local native_dim = self:getNativePageDimensions(pageno) - used.x0, used.y0, used.x1, used.y1 = 0, 0, native_dim.w, native_dim.h - return used - end -end - --- get reflow context -function KoptDocument:getKOPTContext(pageno) - local kc = KOPTContext.new() - kc:setTrim(self.configurable.trim_page) - kc:setWrap(self.configurable.text_wrap) - kc:setIndent(self.configurable.detect_indent) - kc:setRotate(self.configurable.screen_rotation) - kc:setColumns(self.configurable.max_columns) - kc:setDeviceDim(self.screen_size.w, self.screen_size.h) - kc:setDeviceDPI(self.screen_dpi) - kc:setStraighten(self.configurable.auto_straighten) - kc:setJustification(self.configurable.justification) - kc:setZoom(self.configurable.font_size) - kc:setMargin(self.configurable.page_margin) - kc:setQuality(self.configurable.quality) - kc:setContrast(self.configurable.contrast) - kc:setDefectSize(self.configurable.defect_size) - kc:setLineSpacing(self.configurable.line_spacing) - kc:setWordSpacing(self.configurable.word_spacing) - local bbox = self:getUsedBBox(pageno) - kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1) - return kc -end - --- calculates page dimensions -function KoptDocument:getPageDimensions(pageno, zoom, rotation) - -- check cached page size - local hash = "kctx|"..self.file.."|"..pageno.."|"..self.configurable:hash('|') - local cached = Cache:check(hash) - if not cached then - local kc = self:getKOPTContext(pageno) - local page = self._document:openPage(pageno) - -- reflow page - page:reflow(kc, 0) - page:close() - local fullwidth, fullheight = kc:getPageDim() - DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight) - local page_size = Geom:new{ w = fullwidth, h = fullheight } - -- cache reflowed page size and kc - Cache:insert(hash, CacheItem:new{ kctx = kc }) - return page_size - end - --DEBUG("Found cached koptcontex on page", pageno, cached) - local fullwidth, fullheight = cached.kctx:getPageDim() - local page_size = Geom:new{ w = fullwidth, h = fullheight } - return page_size -end - -function KoptDocument:renderPage(pageno, rect, zoom, rotation, render_mode) - self.render_mode = render_mode - local hash = "renderpg|"..self.file.."|"..pageno.."|"..self.configurable:hash('|') - local page_size = self:getPageDimensions(pageno, zoom, rotation) - -- this will be the size we actually render - local size = page_size - -- we prefer to render the full page, if it fits into cache - if not Cache:willAccept(size.w * size.h / 2) then - -- whole page won't fit into cache - DEBUG("rendering only part of the page") - -- TODO: figure out how to better segment the page - if not rect then - DEBUG("aborting, since we do not have a specification for that part") - -- required part not given, so abort - return - end - -- only render required part - hash = "renderpg|"..self.file.."|"..pageno.."|"..self.configurable:hash('|').."|"..tostring(rect) - size = rect - end - - local cached = Cache:check(hash) - if cached then return cached end - - -- prepare cache item with contained blitbuffer - local tile = CacheItem:new{ - size = size.w * size.h / 2 + 64, -- estimation - excerpt = size, - pageno = pageno, - bb = Blitbuffer.new(size.w, size.h) - } - - -- draw to blitbuffer - local kc_hash = "kctx|"..self.file.."|"..pageno.."|"..self.configurable:hash('|') - local page = self._document:openPage(pageno) - local cached = Cache:check(kc_hash) - if cached then - page:rfdraw(cached.kctx, tile.bb) - page:close() - DEBUG("cached hash", hash) - if not Cache:check(hash) then - Cache:insert(hash, tile) - end - return tile - end - DEBUG("Error: cannot render page before reflowing.") -end - -DocumentRegistry:addProvider("pdf", "application/pdf", KoptDocument) -DocumentRegistry:addProvider("djvu", "application/djvu", KoptDocument) diff --git a/frontend/document/koptinterface.lua b/frontend/document/koptinterface.lua new file mode 100644 index 000000000..a45951b3f --- /dev/null +++ b/frontend/document/koptinterface.lua @@ -0,0 +1,113 @@ +require "cache" +require "ui/geometry" +require "ui/screen" +require "ui/device" +require "ui/reader/readerconfig" + +KoptInterface = {} + +-- get reflow context +function KoptInterface:getKOPTContext(doc, pageno) + local kc = KOPTContext.new() + kc:setTrim(doc.configurable.trim_page) + kc:setWrap(doc.configurable.text_wrap) + kc:setIndent(doc.configurable.detect_indent) + kc:setRotate(doc.configurable.screen_rotation) + kc:setColumns(doc.configurable.max_columns) + kc:setDeviceDim(doc.screen_size.w, doc.screen_size.h) + kc:setDeviceDPI(doc.screen_dpi) + kc:setStraighten(doc.configurable.auto_straighten) + kc:setJustification(doc.configurable.justification) + kc:setZoom(doc.configurable.font_size) + kc:setMargin(doc.configurable.page_margin) + kc:setQuality(doc.configurable.quality) + kc:setContrast(doc.configurable.contrast) + kc:setDefectSize(doc.configurable.defect_size) + kc:setLineSpacing(doc.configurable.line_spacing) + kc:setWordSpacing(doc.configurable.word_spacing) + local bbox = doc:getUsedBBox(pageno) + kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1) + return kc +end + +-- calculates page dimensions +function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation) + -- check cached page size + local hash = "kctx|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|') + local cached = Cache:check(hash) + if not cached then + local kc = self:getKOPTContext(doc, pageno) + local page = doc._document:openPage(pageno) + -- reflow page + page:reflow(kc, 0) + page:close() + local fullwidth, fullheight = kc:getPageDim() + DEBUG("page::reflowPage:", "fullwidth:", fullwidth, "fullheight:", fullheight) + local page_size = Geom:new{ w = fullwidth, h = fullheight } + -- cache reflowed page size and kc + Cache:insert(hash, CacheItem:new{ kctx = kc }) + return page_size + end + --DEBUG("Found cached koptcontex on page", pageno, cached) + local fullwidth, fullheight = cached.kctx:getPageDim() + local page_size = Geom:new{ w = fullwidth, h = fullheight } + return page_size +end + +function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode) + doc.render_mode = render_mode + local hash = "renderpg|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|') + local page_size = self:getPageDimensions(doc, pageno, zoom, rotation) + -- this will be the size we actually render + local size = page_size + -- we prefer to render the full page, if it fits into cache + if not Cache:willAccept(size.w * size.h / 2) then + -- whole page won't fit into cache + DEBUG("rendering only part of the page") + -- TODO: figure out how to better segment the page + if not rect then + DEBUG("aborting, since we do not have a specification for that part") + -- required part not given, so abort + return + end + -- only render required part + hash = "renderpg|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|').."|"..tostring(rect) + size = rect + end + + local cached = Cache:check(hash) + if cached then return cached end + + -- prepare cache item with contained blitbuffer + local tile = CacheItem:new{ + size = size.w * size.h / 2 + 64, -- estimation + excerpt = size, + pageno = pageno, + bb = Blitbuffer.new(size.w, size.h) + } + + -- draw to blitbuffer + local kc_hash = "kctx|"..doc.file.."|"..pageno.."|"..doc.configurable:hash('|') + local page = doc._document:openPage(pageno) + local cached = Cache:check(kc_hash) + if cached then + page:rfdraw(cached.kctx, tile.bb) + page:close() + DEBUG("cached hash", hash) + if not Cache:check(hash) then + Cache:insert(hash, tile) + end + return tile + end + DEBUG("Error: cannot render page before reflowing.") +end + +function KoptInterface:drawPage(doc, target, x, y, rect, pageno, zoom, rotation, render_mode) + local tile = self:renderPage(doc, pageno, rect, zoom, rotation, render_mode) + DEBUG("now painting", tile, rect) + target:blitFrom(tile.bb, + x, y, + rect.x - tile.excerpt.x, + rect.y - tile.excerpt.y, + rect.w, rect.h) +end diff --git a/frontend/document/pdfdocument.lua b/frontend/document/pdfdocument.lua index 17d2eb4df..85709c5a6 100644 --- a/frontend/document/pdfdocument.lua +++ b/frontend/document/pdfdocument.lua @@ -1,14 +1,23 @@ require "cache" require "ui/geometry" +require "ui/screen" +require "ui/device" +require "ui/reader/readerconfig" +require "document/koptinterface" PdfDocument = Document:new{ _document = false, -- muPDF manages its own additional cache mupdf_cache_size = 5 * 1024 * 1024, - dc_null = DrawContext.new() + dc_null = DrawContext.new(), + screen_size = Screen:getSize(), + screen_dpi = Device:getModel() == "KindlePaperWhite" and 212 or 167, + configurable = Configurable, + koptinterface = KoptInterface, } function PdfDocument:init() + self.configurable:loadDefaults() local ok ok, self._document = pcall(pdf.openDocument, self.file, self.mupdf_cache_size) if not ok then @@ -17,6 +26,7 @@ function PdfDocument:init() end self.is_open = true self.info.has_pages = true + self.info.configurable = true if self._document:needsPassword() then self.is_locked = true else @@ -50,4 +60,28 @@ function PdfDocument:getUsedBBox(pageno) return used end +function PdfDocument:getPageDimensions(pageno, zoom, rotation) + if self.configurable.text_wrap == 1 then + return self.koptinterface:getPageDimensions(self, pageno, zoom, rotation) + else + return Document.getPageDimensions(self, pageno, zoom, rotation) + end +end + +function PdfDocument:renderPage(pageno, rect, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + return self.koptinterface:renderPage(self, pageno, rect, zoom, rotation, render_mode) + else + return Document.renderPage(self, pageno, rect, zoom, rotation, render_mode) + end +end + +function PdfDocument:drawPage(target, x, y, rect, pageno, zoom, rotation, render_mode) + if self.configurable.text_wrap == 1 then + self.koptinterface:drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + else + Document.drawPage(self, target, x, y, rect, pageno, zoom, rotation, render_mode) + end +end + DocumentRegistry:addProvider("pdf", "application/pdf", PdfDocument) diff --git a/frontend/ui/reader/readerconfig.lua b/frontend/ui/reader/readerconfig.lua index 5e1f3b26f..b13ce0dc3 100644 --- a/frontend/ui/reader/readerconfig.lua +++ b/frontend/ui/reader/readerconfig.lua @@ -46,8 +46,8 @@ KOPTOptions = { name_text = "Reflow", item_text = {"on","off"}, values = {1, 0}, - default_value = 1, - show = false + default_value = 0, + show = true }, { name = "max_columns", @@ -153,6 +153,48 @@ KOPTOptions = { }, } +Configurable = {} + +function Configurable:hash(sep) + local hash = "" + local excluded = {multi_threads = true,} + for key,value in pairs(self) do + if type(value) == "number" and not excluded[key] then + hash = hash..sep..value + end + end + return hash +end + +function Configurable:loadDefaults() + for i=1,#KOPTOptions do + local options = KOPTOptions[i].options + for j=1,#KOPTOptions[i].options do + local key = KOPTOptions[i].options[j].name + self[key] = KOPTOptions[i].options[j].default_value + end + end +end + +function Configurable:loadSettings(settings, prefix) + for key,value in pairs(self) do + if type(value) == "number" then + saved_value = settings:readSetting(prefix..key) + self[key] = (saved_value == nil) and self[key] or saved_value + --Debug("Configurable:loadSettings", "key", key, "saved value", saved_value,"Configurable.key", self[key]) + end + end + --Debug("loaded config:", dump(Configurable)) +end + +function Configurable:saveSettings(settings, prefix) + for key,value in pairs(self) do + if type(value) == "number" then + settings:saveSetting(prefix..key, value) + end + end +end + ReaderConfig = InputContainer:new{ dimen = Geom:new{ x = 0, @@ -189,7 +231,9 @@ function ReaderConfig:onShowConfigMenu() function config_dialog:onConfigChoice(option_name, option_value) self.configurable[option_name] = option_value - --DEBUG("configurable", self.configurable) + if option_name == "text_wrap" then + self.ui:handleEvent(Event:new("RedrawCurrentPage")) + end end local dialog_container = CenterContainer:new{ @@ -199,7 +243,7 @@ function ReaderConfig:onShowConfigMenu() config_dialog.close_callback = function () UIManager:close(menu_container) end - -- maintain a reference to menu_container + self.dialog_container = dialog_container UIManager:show(config_dialog) @@ -218,7 +262,6 @@ function ReaderConfig:onSetDimensions(dimen) end function ReaderConfig:onReadSettings(config) - DEBUG("read setting", config) self.configurable:loadSettings(config, 'kopt_') end diff --git a/frontend/ui/reader/readerpaging.lua b/frontend/ui/reader/readerpaging.lua index 64f804180..f9da309e7 100644 --- a/frontend/ui/reader/readerpaging.lua +++ b/frontend/ui/reader/readerpaging.lua @@ -227,5 +227,6 @@ function ReaderPaging:onGotoPageRel(diff) return true end - - +function ReaderPaging:onRedrawCurrentPage() + self.ui:handleEvent(Event:new("PageUpdate", self.current_page)) +end