diff --git a/djvureader.lua b/djvureader.lua new file mode 100644 index 000000000..f34c434f0 --- /dev/null +++ b/djvureader.lua @@ -0,0 +1,75 @@ +require "unireader" + +DJVUReader = UniReader:new{ + newDC = function() + print("djvu.newDC") + return djvu.newDC() + end, +} + +function DJVUReader:init() + self.nulldc = self.newDC() +end + +-- open a DJVU file and its settings store +function DJVUReader:open(filename) + self.doc = djvu.openDocument(filename) + if self.doc ~= nil then + self.settings = DocSettings:open(filename) + local gamma = self.settings:readsetting("gamma") + if gamma then + self.globalgamma = gamma + end + return true + end + return false +end + +--set viewer state according to zoom state +function DJVUReader:setzoom(page) + local dc = self.newDC() + local pwidth, pheight = page:getSize(self.nulldc) + + if self.globalzoommode == self.ZOOM_FIT_TO_PAGE then + self.globalzoom = width / pwidth + self.offset_x = 0 + self.offset_y = (height - (self.globalzoom * pheight)) / 2 + if height / pheight < self.globalzoom then + self.globalzoom = height / pheight + print(width, (self.globalzoom * pwidth)) + self.offset_x = (width - (self.globalzoom * pwidth)) / 2 + self.offset_y = 0 + end + elseif self.globalzoommode == self.ZOOM_FIT_TO_PAGE_WIDTH then + self.globalzoom = width / pwidth + self.offset_x = 0 + self.offset_y = (height - (self.globalzoom * pheight)) / 2 + elseif self.globalzoommode == self.ZOOM_FIT_TO_PAGE_HEIGHT then + self.globalzoom = height / pheight + self.offset_x = (width - (self.globalzoom * pwidth)) / 2 + self.offset_y = 0 + end + + dc:setZoom(self.globalzoom) + -- record globalzoom for manual zoom in/out + self.globalzoom_orig = self.globalzoom + + dc:setRotate(self.globalrotate); + dc:setOffset(self.offset_x, self.offset_y) + self.fullwidth, self.fullheight = page:getSize(dc) + self.min_offset_x = fb.bb:getWidth() - self.fullwidth + self.min_offset_y = fb.bb:getHeight() - self.fullheight + if(self.min_offset_x > 0) then + self.min_offset_x = 0 + end + if(self.min_offset_y > 0) then + self.min_offset_y = 0 + end + + -- set gamma here, we don't have any other good place for this right now: + if self.globalgamma ~= self.GAMMA_NO_GAMMA then + print("gamma correction: "..self.globalgamma) + dc:setGamma(self.globalgamma) + end + return dc +end diff --git a/filesearcher.lua b/filesearcher.lua index 815a26603..e0f41d8de 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -261,12 +261,8 @@ function FileSearcher:choose(ypos, height, keywords) pagedirty = true elseif ev.code == KEY_ENTER or ev.code == KEY_FW_PRESS then file_entry = self.result[perpage*(self.page-1)+self.current] - file_path = file_entry.dir .. "/" .. file_entry.name - - if PDFReader:open(file_path,"") then -- TODO: query for password - PDFReader:goto(tonumber(PDFReader.settings:readsetting("last_page") or 1)) - PDFReader:inputloop() - end + file_full_path = file_entry.dir .. "/" .. file_entry.name + openFile(file_full_path) pagedirty = true elseif ev.code == KEY_BACK then diff --git a/pdfreader.lua b/pdfreader.lua index 9db9d0f1d..8973cfadf 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -1,147 +1,19 @@ -require "keys" -require "settings" ---require "tocmenu" -require "selectmenu" +require "unireader" -PDFReader = { - -- "constants": - ZOOM_BY_VALUE = 0, - ZOOM_FIT_TO_PAGE = -1, - ZOOM_FIT_TO_PAGE_WIDTH = -2, - ZOOM_FIT_TO_PAGE_HEIGHT = -3, - ZOOM_FIT_TO_CONTENT = -4, - ZOOM_FIT_TO_CONTENT_WIDTH = -5, - ZOOM_FIT_TO_CONTENT_HEIGHT = -6, - ZOOM_FIT_TO_CONTENT_HALF_WIDTH = -7, - - GAMMA_NO_GAMMA = 1.0, - - -- framebuffer update policy state: - rcount = 5, - rcountmax = 5, - - -- zoom state: - globalzoom = 1.0, - globalzoommode = -1, -- ZOOM_FIT_TO_PAGE - - globalrotate = 0, - - -- gamma setting: - globalgamma = 1.0, -- GAMMA_NO_GAMMA - - -- size of current page for current zoom level in pixels - fullwidth = 0, - fullheight = 0, - offset_x = 0, - offset_y = 0, - min_offset_x = 0, - min_offset_y = 0, - - -- set panning distance - shift_x = 100, - shift_y = 50, - pan_by_page = false, -- using shift_[xy] or width/height - pan_x = 0, -- top-left offset of page when pan activated - pan_y = 0, - pan_margin = 20, - - -- the pdf document: - doc = nil, - -- the document's setting store: - settings = nil, - - -- we will use this one often, so keep it "static": - nulldc = nil, - newDC = nil, - - -- tile cache configuration: - cache_max_memsize = 1024*1024*5, -- 5MB tile cache - cache_item_max_pixels = 1024*1024*2, -- max. size of rendered tiles - cache_max_ttl = 20, -- time to live - -- tile cache state: - cache_current_memsize = 0, - cache = {}, - jump_stack = {}, +PDFReader = UniReader:new{ + newDC = function() + print("pdf.newDC") + return pdf.newDC() + end, } --- guarantee that we have enough memory in cache -function PDFReader:cacheclaim(size) - if(size > self.cache_max_memsize) then - -- we're not allowed to claim this much at all - error("too much memory claimed") - return false - end - while self.cache_current_memsize + size > self.cache_max_memsize do - -- repeat this until we have enough free memory - for k, _ in pairs(self.cache) do - if self.cache[k].ttl > 0 then - -- reduce ttl - self.cache[k].ttl = self.cache[k].ttl - 1 - else - -- cache slot is at end of life, so kick it out - self.cache_current_memsize = self.cache_current_memsize - self.cache[k].size - self.cache[k] = nil - end - end - end - self.cache_current_memsize = self.cache_current_memsize + size - return true -end - -function PDFReader:draworcache(no, zoom, offset_x, offset_y, width, height, gamma, rotate) - -- hash draw state - local hash = self:cachehash(no, zoom, offset_x, offset_y, width, height, gamma, rotate) - if self.cache[hash] == nil then - -- not in cache, so prepare cache slot... - self:cacheclaim(width * height / 2); - self.cache[hash] = { - ttl = self.cache_max_ttl, - size = width * height / 2, - bb = Blitbuffer.new(width, height) - } - -- and draw the page - local page = self.doc:openPage(no) - local dc = self:setzoom(page, hash) - page:draw(dc, self.cache[hash].bb, 0, 0) - page:close() - else - -- we have the page in our cache, - -- so give it more ttl. - self.cache[hash].ttl = self.cache_max_ttl - end - return hash -end - --- calculate a hash for our current state -function PDFReader:cachehash(no, zoom, offset_x, offset_y, width, height, gamma, rotate) - -- TODO (?): make this a "real" hash... - return no..'_'..zoom..'_'..offset_x..','..offset_y..'-'..width..'x'..height..'_'..gamma..'_'..rotate -end - --- blank the cache -function PDFReader:clearcache() - self.cache = {} - self.cache_current_memsize = 0 +function PDFReader:init() + self.nulldc = self.newDC(); end -- open a PDF file and its settings store function PDFReader:open(filename, password) - if string.match(filename, ".+%.[dD][jJ][vV][uU]$") then - self.doc = djvu.openDocument(filename) - self.newDC = function() - print("djvu.newDC") - return djvu.newDC() - end - else - self.doc = pdf.openDocument(filename, password or "") - self.newDC = function() - print("pdf.newDC") - return pdf.newDC() - end - end - - self.nulldc = self.newDC(); - + self.doc = pdf.openDocument(filename, password or "") if self.doc ~= nil then self.settings = DocSettings:open(filename) local gamma = self.settings:readsetting("gamma") @@ -152,431 +24,3 @@ function PDFReader:open(filename, password) end return false end - --- set viewer state according to zoom state -function PDFReader:setzoom(page) - local dc = self.newDC() - local pwidth, pheight = page:getSize(self.nulldc) - print("# page::getSize "..pwidth.."*"..pheight); - local x0, y0, x1, y1 = page:getUsedBBox() - if x0 == 0.01 and y0 == 0.01 and x1 == -0.01 and y1 == -0.01 then - x0 = 0 - y0 = 0 - x1 = pwidth - y1 = pheight - end - -- clamp to page BBox - if x0 < 0 then x0 = 0 end - if x1 > pwidth then x1 = pwidth end - if y0 < 0 then y0 = 0 end - if y1 > pheight then y1 = pheight end - - print("# page::getUsedBBox "..x0.."*"..y0.." "..x1.."*"..y1); - - if self.globalzoommode == self.ZOOM_FIT_TO_PAGE - or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT then - self.globalzoom = width / pwidth - self.offset_x = 0 - self.offset_y = (height - (self.globalzoom * pheight)) / 2 - if height / pheight < self.globalzoom then - self.globalzoom = height / pheight - self.offset_x = (width - (self.globalzoom * pwidth)) / 2 - self.offset_y = 0 - end - elseif self.globalzoommode == self.ZOOM_FIT_TO_PAGE_WIDTH - or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_WIDTH then - self.globalzoom = width / pwidth - self.offset_x = 0 - self.offset_y = (height - (self.globalzoom * pheight)) / 2 - elseif self.globalzoommode == self.ZOOM_FIT_TO_PAGE_HEIGHT - or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HEIGHT then - self.globalzoom = height / pheight - self.offset_x = (width - (self.globalzoom * pwidth)) / 2 - self.offset_y = 0 - end - if self.globalzoommode == self.ZOOM_FIT_TO_CONTENT then - if (x1 - x0) < pwidth then - self.globalzoom = width / (x1 - x0) - self.offset_x = -1 * x0 * self.globalzoom - self.offset_y = -1 * y0 * self.globalzoom + (height - (self.globalzoom * (y1 - y0))) / 2 - if height / (y1 - y0) < self.globalzoom then - self.globalzoom = height / (y1 - y0) - self.offset_x = -1 * x0 * self.globalzoom + (width - (self.globalzoom * (x1 - x0))) / 2 - self.offset_y = -1 * y0 * self.globalzoom - end - end - elseif self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_WIDTH then - if (x1 - x0) < pwidth then - self.globalzoom = width / (x1 - x0) - self.offset_x = -1 * x0 * self.globalzoom - self.offset_y = -1 * y0 * self.globalzoom + (height - (self.globalzoom * (y1 - y0))) / 2 - end - elseif self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HEIGHT then - if (y1 - y0) < pheight then - self.globalzoom = height / (y1 - y0) - self.offset_x = -1 * x0 * self.globalzoom + (width - (self.globalzoom * (x1 - x0))) / 2 - self.offset_y = -1 * y0 * self.globalzoom - end - elseif self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH then - self.globalzoom = width / (x1 - x0 + self.pan_margin) - self.offset_x = -1 * x0 * self.globalzoom * 2 + self.pan_margin - self.globalzoom = height / (y1 - y0) - self.offset_y = -1 * y0 * self.globalzoom * 2 + self.pan_margin - self.globalzoom = width / (x1 - x0 + self.pan_margin) * 2 - print("column mode offset:"..self.offset_x.."*"..self.offset_y.." zoom:"..self.globalzoom); - self.globalzoommode = self.ZOOM_BY_VALUE -- enable pan mode - self.pan_x = self.offset_x - self.pan_y = self.offset_y - self.pan_by_page = true - end - dc:setZoom(self.globalzoom) - dc:setRotate(self.globalrotate); - dc:setOffset(self.offset_x, self.offset_y) - self.fullwidth, self.fullheight = page:getSize(dc) - self.min_offset_x = fb.bb:getWidth() - self.fullwidth - self.min_offset_y = fb.bb:getHeight() - self.fullheight - if(self.min_offset_x > 0) then - self.min_offset_x = 0 - end - if(self.min_offset_y > 0) then - self.min_offset_y = 0 - end - - print("# PDFReader:setzoom globalzoom:"..self.globalzoom.." globalrotate:"..self.globalrotate.." offset:"..self.offset_x.."*"..self.offset_y.." pagesize:"..self.fullwidth.."*"..self.fullheight.." min_offset:"..self.min_offset_x.."*"..self.min_offset_y) - - -- set gamma here, we don't have any other good place for this right now: - if self.globalgamma ~= self.GAMMA_NO_GAMMA then - print("gamma correction: "..self.globalgamma) - dc:setGamma(self.globalgamma) - end - return dc -end - --- render and blit a page -function PDFReader:show(no) - local slot - if self.globalzoommode ~= self.ZOOM_BY_VALUE then - slot = self:draworcache(no,self.globalzoommode,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) - else - slot = self:draworcache(no,self.globalzoom,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) - end - fb.bb:blitFullFrom(self.cache[slot].bb) - if self.rcount == self.rcountmax then - print("full refresh") - self.rcount = 1 - fb:refresh(0) - else - print("partial refresh") - self.rcount = self.rcount + 1 - fb:refresh(1) - end - self.slot_visible = slot; -end - -function PDFReader:add_jump(pageno) - local jump_item = nil - -- add current page to jump_stack if no in - for _t,_v in ipairs(self.jump_stack) do - if _v.page == pageno then - jump_item = _v - table.remove(self.jump_stack, _t) - elseif _v.page == no then - -- the page we jumped to should not be show in stack - table.remove(self.jump_stack, _t) - end - end - -- create a new one if not found - if not jump_item then - jump_item = { - page = pageno, - datetime = os.date("%Y-%m-%d %H:%M:%S"), - } - end - -- insert at the start - table.insert(self.jump_stack, 1, jump_item) - if #self.jump_stack > 10 then - -- remove the last element to keep the size less than 10 - table.remove(self.jump_stack) - end -end - --- change current page and cache next page after rendering -function PDFReader:goto(no) - if no < 1 or no > self.doc:getPages() then - return - end - - -- for jump_stack - if self.pageno and math.abs(self.pageno - no) > 1 then - self:add_jump(self.pageno) - end - - self.pageno = no - self:show(no) - if no < self.doc:getPages() then - if self.globalzoommode ~= self.ZOOM_BY_VALUE then - -- pre-cache next page - self:draworcache(no+1,self.globalzoommode,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) - else - self:draworcache(no,self.globalzoom,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) - end - end -end - --- adjust global gamma setting -function PDFReader:modify_gamma(factor) - print("modify_gamma, gamma="..self.globalgamma.." factor="..factor) - self.globalgamma = self.globalgamma * factor; - self:goto(self.pageno) -end - --- adjust zoom state and trigger re-rendering -function PDFReader:setglobalzoommode(newzoommode) - if self.globalzoommode ~= newzoommode then - self.globalzoommode = newzoommode - self:goto(self.pageno) - end -end - --- adjust zoom state and trigger re-rendering -function PDFReader:setglobalzoom(zoom) - if self.globalzoom ~= zoom then - self.globalzoommode = self.ZOOM_BY_VALUE - self.globalzoom = zoom - self:goto(self.pageno) - end -end - -function PDFReader:setrotate(rotate) - self.globalrotate = rotate - self:goto(self.pageno) -end - -function PDFReader:showTOC() - toc = self.doc:getTOC() - local menu_items = {} - -- build menu items - for _k,_v in ipairs(toc) do - table.insert(menu_items, - (" "):rep(_v.depth-1) .. _v.title:gsub("\13", "")) - end - toc_menu = SelectMenu:new{ - menu_title = "Table of Contents", - item_array = menu_items, - no_item_msg = "This document does not have a Table of Contents.", - } - item_no = toc_menu:choose(0, fb.bb:getHeight()) - if item_no then - self:goto(toc[item_no].page) - else - self:goto(self.pageno) - end -end - -function PDFReader:showJumpStack() - local menu_items = {} - for _k,_v in ipairs(self.jump_stack) do - table.insert(menu_items, - _v.datetime.." -> Page ".._v.page) - end - jump_menu = SelectMenu:new{ - menu_title = "Jump Keeper (current page: "..self.pageno..")", - item_array = menu_items, - no_item_msg = "No jump history.", - } - item_no = jump_menu:choose(0, fb.bb:getHeight()) - if item_no then - local jump_item = self.jump_stack[item_no] - self:goto(jump_item.page) - else - self:goto(self.pageno) - end -end - - --- wait for input and handle it -function PDFReader:inputloop() - local keep_running = true - while 1 do - local ev = input.waitForEvent() - ev.code = adjustKeyEvents(ev) - if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - local secs, usecs = util.gettime() - if ev.code == KEY_PGFWD or ev.code == KEY_LPGFWD then - if Keys.shiftmode then - self:setglobalzoom(self.globalzoom+0.2) - elseif Keys.altmode then - self:setglobalzoom(self.globalzoom+0.1) - else - if self.pan_by_page then - self.offset_x = self.pan_x - self.offset_y = self.pan_y - end - self:goto(self.pageno + 1) - end - elseif ev.code == KEY_PGBCK or ev.code == KEY_LPGBCK then - if Keys.shiftmode then - self:setglobalzoom(self.globalzoom-0.2) - elseif Keys.altmode then - self:setglobalzoom(self.globalzoom-0.1) - else - if self.pan_by_page then - self.offset_x = self.pan_x - self.offset_y = self.pan_y - end - self:goto(self.pageno - 1) - end - elseif ev.code == KEY_BACK then - if Keys.altmode then - -- altmode, exit pdfreader - break - else - -- not altmode, back to last jump - if #self.jump_stack ~= 0 then - self:goto(self.jump_stack[1].page) - end - end - elseif ev.code == KEY_VPLUS then - self:modify_gamma( 1.25 ) - elseif ev.code == KEY_VMINUS then - self:modify_gamma( 0.8 ) - elseif ev.code == KEY_A then - if Keys.shiftmode then - self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT) - else - self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE) - end - elseif ev.code == KEY_S then - if Keys.shiftmode then - self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_WIDTH) - else - self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE_WIDTH) - end - elseif ev.code == KEY_D then - if Keys.shiftmode then - self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HEIGHT) - else - self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE_HEIGHT) - end - elseif ev.code == KEY_F then - self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH) - elseif ev.code == KEY_T then - self:showTOC() - elseif ev.code == KEY_B then - if Keys.shiftmode then - self:add_jump(self.pageno) - else - self:showJumpStack() - end - elseif ev.code == KEY_J then - self:setrotate( self.globalrotate + 10 ) - elseif ev.code == KEY_K then - self:setrotate( self.globalrotate - 10 ) - elseif ev.code == KEY_HOME then - -- signal quit - keep_running = false - break - end - - if self.globalzoommode == self.ZOOM_BY_VALUE then - local x - local y - - if Keys.shiftmode then -- shift always moves in small steps - x = self.shift_x / 2 - y = self.shift_y / 2 - elseif Keys.altmode then - x = self.shift_x / 5 - y = self.shift_y / 5 - elseif self.pan_by_page then - x = width; - y = height - self.pan_margin; -- overlap for lines which didn't fit - else - x = self.shift_x - y = self.shift_y - end - - print("offset "..self.offset_x.."*"..self.offset_x.." shift "..x.."*"..y.." globalzoom="..self.globalzoom) - local old_offset_x = self.offset_x - local old_offset_y = self.offset_y - - if ev.code == KEY_FW_LEFT then - self.offset_x = self.offset_x + x - if self.offset_x > 0 then - self.offset_x = 0 - if self.pan_by_page and self.pageno > 1 then - self.offset_x = self.pan_x - self.offset_y = self.min_offset_y -- bottom - self:goto(self.pageno - 1) - end - end - if self.pan_by_page then - self.offset_y = self.min_offset_y - end - elseif ev.code == KEY_FW_RIGHT then - print("# FW RIGHT "..self.offset_x.." - "..x.." < "..self.min_offset_x); - self.offset_x = self.offset_x - x - if self.offset_x < self.min_offset_x - self.pan_margin then - self.offset_x = self.min_offset_x - if self.pan_by_page and self.pageno < self.doc:getPages() then - self.offset_x = self.pan_x - self.offset_y = self.pan_y - self:goto(self.pageno + 1) - end - end - if self.pan_by_page then - self.offset_y = self.pan_y - end - elseif ev.code == KEY_FW_UP then - self.offset_y = self.offset_y + y - if self.offset_y > 0 then - self.offset_y = 0 - end - elseif ev.code == KEY_FW_DOWN then - self.offset_y = self.offset_y - y - if self.offset_y < self.min_offset_y then - self.offset_y = self.min_offset_y - end - elseif ev.code == KEY_FW_PRESS then - if Keys.shiftmode then - if self.pan_by_page then - self.offset_x = self.pan_x - self.offset_y = self.pan_y - else - self.offset_x = 0 - self.offset_y = 0 - end - else - self.pan_by_page = not self.pan_by_page - if self.pan_by_page then - self.pan_x = self.offset_x - self.pan_y = self.offset_y - end - end - end - if old_offset_x ~= self.offset_x - or old_offset_y ~= self.offset_y then - self:goto(self.pageno) - end - end - - local nsecs, nusecs = util.gettime() - local dur = (nsecs - secs) * 1000000 + nusecs - usecs - print("E: T="..ev.type.." V="..ev.value.." C="..ev.code.." DUR="..dur) - end - end - - self:clearcache() - if self.doc ~= nil then - self.doc:close() - end - if self.settings ~= nil then - self.settings:savesetting("last_page", self.pageno) - self.settings:savesetting("gamma", self.globalgamma) - self.settings:close() - end - - return keep_running -end - - diff --git a/reader.lua b/reader.lua index 1863eae8c..e8adcae54 100755 --- a/reader.lua +++ b/reader.lua @@ -19,6 +19,7 @@ require "alt_getopt" require "pdfreader" +require "djvureader" require "filechooser" require "settings" @@ -30,6 +31,25 @@ longopts = { device = "d", help = "h" } + +function openFile(filename) + local file_type = string.lower(string.match(filename, ".+%.(.+)")) + if file_type == "djvu" then + print "haha" + if DJVUReader:open(filename) then + page_num = DJVUReader.settings:readsetting("last_page") or 1 + DJVUReader:goto(tonumber(page_num)) + DJVUReader:inputloop() + end + elseif file_type == "pdf" then + if PDFReader:open(filename,"") then -- TODO: query for password + page_num = PDFReader.settings:readsetting("last_page") or 1 + PDFReader:goto(tonumber(page_num)) + PDFReader:inputloop() + end + end +end + optarg, optind = alt_getopt.get_opts(ARGV, "p:G:hg:d:", longopts) if optarg["h"] or ARGV[optind] == nil then print("usage: ./reader.lua [OPTION] ... DOCUMENT.PDF") @@ -52,6 +72,7 @@ if optarg["h"] or ARGV[optind] == nil then return end + if optarg["d"] == "k3" then -- for now, the only difference is the additional input device input.open("/dev/input/event0") @@ -91,27 +112,27 @@ if r_cfont ~=nil then FontChooser.cfont = r_cfont end +-- initialize specific readers +PDFReader:init() +DJVUReader:init() +-- display directory or open file if lfs.attributes(ARGV[optind], "mode") == "directory" then local running = true FileChooser:setPath(ARGV[optind]) while running do - local pdffile = FileChooser:choose(0,height) - if pdffile ~= nil then - if PDFReader:open(pdffile,"") then -- TODO: query for password - PDFReader:goto(tonumber(PDFReader.settings:readsetting("last_page") or 1)) - running = PDFReader:inputloop() - end + local file = FileChooser:choose(0,height) + if file ~= nil then + openFile(file) else running = false end end else - PDFReader:open(ARGV[optind], optarg["p"]) - PDFReader:goto(tonumber(optarg["g"]) or tonumber(PDFReader.settings:readsetting("last_page") or 1)) - PDFReader:inputloop() + openFile(ARGV[optind], optarg["p"]) end + -- save reader settings reader_settings:savesetting("cfont", FontChooser.cfont) reader_settings:close() diff --git a/unireader.lua b/unireader.lua new file mode 100644 index 000000000..3d476c2ae --- /dev/null +++ b/unireader.lua @@ -0,0 +1,582 @@ +require "keys" +require "settings" +require "selectmenu" + +UniReader = { + -- "constants": + ZOOM_BY_VALUE = 0, + ZOOM_FIT_TO_PAGE = -1, + ZOOM_FIT_TO_PAGE_WIDTH = -2, + ZOOM_FIT_TO_PAGE_HEIGHT = -3, + ZOOM_FIT_TO_CONTENT = -4, + ZOOM_FIT_TO_CONTENT_WIDTH = -5, + ZOOM_FIT_TO_CONTENT_HEIGHT = -6, + ZOOM_FIT_TO_CONTENT_HALF_WIDTH = -7, + + GAMMA_NO_GAMMA = 1.0, + + -- framebuffer update policy state: + rcount = 5, + rcountmax = 5, + + -- zoom state: + globalzoom = 1.0, + globalzoom_orig = 1.0, + globalzoommode = -1, -- ZOOM_FIT_TO_PAGE + + globalrotate = 0, + + -- gamma setting: + globalgamma = 1.0, -- GAMMA_NO_GAMMA + + -- size of current page for current zoom level in pixels + fullwidth = 0, + fullheight = 0, + offset_x = 0, + offset_y = 0, + min_offset_x = 0, + min_offset_y = 0, + + -- set panning distance + shift_x = 100, + shift_y = 50, + pan_by_page = false, -- using shift_[xy] or width/height + pan_x = 0, -- top-left offset of page when pan activated + pan_y = 0, + pan_margin = 20, + + -- the document: + doc = nil, + -- the document's setting store: + settings = nil, + + -- you have to initialize newDC, nulldc in specific reader + newDC = function() return nil end, + -- we will use this one often, so keep it "static": + nulldc = nil, + + -- tile cache configuration: + cache_max_memsize = 1024*1024*5, -- 5MB tile cache + cache_item_max_pixels = 1024*1024*2, -- max. size of rendered tiles + cache_max_ttl = 20, -- time to live + -- tile cache state: + cache_current_memsize = 0, + cache = {}, + jump_stack = {}, +} + +function UniReader:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +function UniReader:init() + print("empty initialization method!") +end + +-- guarantee that we have enough memory in cache +function UniReader:cacheclaim(size) + if(size > self.cache_max_memsize) then + -- we're not allowed to claim this much at all + error("too much memory claimed") + return false + end + while self.cache_current_memsize + size > self.cache_max_memsize do + -- repeat this until we have enough free memory + for k, _ in pairs(self.cache) do + if self.cache[k].ttl > 0 then + -- reduce ttl + self.cache[k].ttl = self.cache[k].ttl - 1 + else + -- cache slot is at end of life, so kick it out + self.cache_current_memsize = self.cache_current_memsize - self.cache[k].size + self.cache[k] = nil + end + end + end + self.cache_current_memsize = self.cache_current_memsize + size + return true +end + +function UniReader:draworcache(no, zoom, offset_x, offset_y, width, height, gamma, rotate) + -- hash draw state + local hash = self:cachehash(no, zoom, offset_x, offset_y, width, height, gamma, rotate) + if self.cache[hash] == nil then + -- not in cache, so prepare cache slot... + self:cacheclaim(width * height / 2); + self.cache[hash] = { + ttl = self.cache_max_ttl, + size = width * height / 2, + bb = Blitbuffer.new(width, height) + } + -- and draw the page + local page = self.doc:openPage(no) + local dc = self:setzoom(page, hash) + page:draw(dc, self.cache[hash].bb, 0, 0) + page:close() + else + -- we have the page in our cache, + -- so give it more ttl. + self.cache[hash].ttl = self.cache_max_ttl + end + return hash +end + +-- calculate a hash for our current state +function UniReader:cachehash(no, zoom, offset_x, offset_y, width, height, gamma, rotate) + -- TODO (?): make this a "real" hash... + return no..'_'..zoom..'_'..offset_x..','..offset_y..'-'..width..'x'..height..'_'..gamma..'_'..rotate +end + +-- blank the cache +function UniReader:clearcache() + self.cache = {} + self.cache_current_memsize = 0 +end + +-- open a file and its settings store +function UniReader:open(filename, password) + return false +end + +-- set viewer state according to zoom state +function UniReader:setzoom(page) + local dc = self.newDC() + local pwidth, pheight = page:getSize(self.nulldc) + print("# page::getSize "..pwidth.."*"..pheight); + local x0, y0, x1, y1 = page:getUsedBBox() + if x0 == 0.01 and y0 == 0.01 and x1 == -0.01 and y1 == -0.01 then + x0 = 0 + y0 = 0 + x1 = pwidth + y1 = pheight + end + -- clamp to page BBox + if x0 < 0 then x0 = 0 end + if x1 > pwidth then x1 = pwidth end + if y0 < 0 then y0 = 0 end + if y1 > pheight then y1 = pheight end + + print("# page::getUsedBBox "..x0.."*"..y0.." "..x1.."*"..y1); + + if self.globalzoommode == self.ZOOM_FIT_TO_PAGE + or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT then + self.globalzoom = width / pwidth + self.offset_x = 0 + self.offset_y = (height - (self.globalzoom * pheight)) / 2 + if height / pheight < self.globalzoom then + self.globalzoom = height / pheight + self.offset_x = (width - (self.globalzoom * pwidth)) / 2 + self.offset_y = 0 + end + elseif self.globalzoommode == self.ZOOM_FIT_TO_PAGE_WIDTH + or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_WIDTH then + self.globalzoom = width / pwidth + self.offset_x = 0 + self.offset_y = (height - (self.globalzoom * pheight)) / 2 + elseif self.globalzoommode == self.ZOOM_FIT_TO_PAGE_HEIGHT + or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HEIGHT then + self.globalzoom = height / pheight + self.offset_x = (width - (self.globalzoom * pwidth)) / 2 + self.offset_y = 0 + end + if self.globalzoommode == self.ZOOM_FIT_TO_CONTENT then + if (x1 - x0) < pwidth then + self.globalzoom = width / (x1 - x0) + self.offset_x = -1 * x0 * self.globalzoom + self.offset_y = -1 * y0 * self.globalzoom + (height - (self.globalzoom * (y1 - y0))) / 2 + if height / (y1 - y0) < self.globalzoom then + self.globalzoom = height / (y1 - y0) + self.offset_x = -1 * x0 * self.globalzoom + (width - (self.globalzoom * (x1 - x0))) / 2 + self.offset_y = -1 * y0 * self.globalzoom + end + end + elseif self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_WIDTH then + if (x1 - x0) < pwidth then + self.globalzoom = width / (x1 - x0) + self.offset_x = -1 * x0 * self.globalzoom + self.offset_y = -1 * y0 * self.globalzoom + (height - (self.globalzoom * (y1 - y0))) / 2 + end + elseif self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HEIGHT then + if (y1 - y0) < pheight then + self.globalzoom = height / (y1 - y0) + self.offset_x = -1 * x0 * self.globalzoom + (width - (self.globalzoom * (x1 - x0))) / 2 + self.offset_y = -1 * y0 * self.globalzoom + end + elseif self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH then + self.globalzoom = width / (x1 - x0 + self.pan_margin) + self.offset_x = -1 * x0 * self.globalzoom * 2 + self.pan_margin + self.globalzoom = height / (y1 - y0) + self.offset_y = -1 * y0 * self.globalzoom * 2 + self.pan_margin + self.globalzoom = width / (x1 - x0 + self.pan_margin) * 2 + print("column mode offset:"..self.offset_x.."*"..self.offset_y.." zoom:"..self.globalzoom); + self.globalzoommode = self.ZOOM_BY_VALUE -- enable pan mode + self.pan_x = self.offset_x + self.pan_y = self.offset_y + self.pan_by_page = true + end + dc:setZoom(self.globalzoom) + dc:setRotate(self.globalrotate); + dc:setOffset(self.offset_x, self.offset_y) + self.fullwidth, self.fullheight = page:getSize(dc) + self.min_offset_x = fb.bb:getWidth() - self.fullwidth + self.min_offset_y = fb.bb:getHeight() - self.fullheight + if(self.min_offset_x > 0) then + self.min_offset_x = 0 + end + if(self.min_offset_y > 0) then + self.min_offset_y = 0 + end + + print("# Reader:setzoom globalzoom:"..self.globalzoom.." globalrotate:"..self.globalrotate.." offset:"..self.offset_x.."*"..self.offset_y.." pagesize:"..self.fullwidth.."*"..self.fullheight.." min_offset:"..self.min_offset_x.."*"..self.min_offset_y) + + -- set gamma here, we don't have any other good place for this right now: + if self.globalgamma ~= self.GAMMA_NO_GAMMA then + print("gamma correction: "..self.globalgamma) + dc:setGamma(self.globalgamma) + end + return dc +end + +-- render and blit a page +function UniReader:show(no) + local slot + if self.globalzoommode ~= self.ZOOM_BY_VALUE then + slot = self:draworcache(no,self.globalzoommode,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) + else + slot = self:draworcache(no,self.globalzoom,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) + end + fb.bb:blitFullFrom(self.cache[slot].bb) + if self.rcount == self.rcountmax then + print("full refresh") + self.rcount = 1 + fb:refresh(0) + else + print("partial refresh") + self.rcount = self.rcount + 1 + fb:refresh(1) + end + self.slot_visible = slot; +end + +function UniReader:add_jump(pageno) + local jump_item = nil + -- add current page to jump_stack if no in + for _t,_v in ipairs(self.jump_stack) do + if _v.page == pageno then + jump_item = _v + table.remove(self.jump_stack, _t) + elseif _v.page == no then + -- the page we jumped to should not be show in stack + table.remove(self.jump_stack, _t) + end + end + -- create a new one if not found + if not jump_item then + jump_item = { + page = pageno, + datetime = os.date("%Y-%m-%d %H:%M:%S"), + } + end + -- insert at the start + table.insert(self.jump_stack, 1, jump_item) + if #self.jump_stack > 10 then + -- remove the last element to keep the size less than 10 + table.remove(self.jump_stack) + end +end + +-- change current page and cache next page after rendering +function UniReader:goto(no) + if no < 1 or no > self.doc:getPages() then + return + end + + -- for jump_stack + if self.pageno and math.abs(self.pageno - no) > 1 then + self:add_jump(self.pageno) + end + + self.pageno = no + self:show(no) + if no < self.doc:getPages() then + if self.globalzoommode ~= self.ZOOM_BY_VALUE then + -- pre-cache next page + self:draworcache(no+1,self.globalzoommode,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) + else + self:draworcache(no,self.globalzoom,self.offset_x,self.offset_y,width,height,self.globalgamma,self.globalrotate) + end + end +end + +-- adjust global gamma setting +function UniReader:modify_gamma(factor) + print("modify_gamma, gamma="..self.globalgamma.." factor="..factor) + self.globalgamma = self.globalgamma * factor; + self:goto(self.pageno) +end + +-- adjust zoom state and trigger re-rendering +function UniReader:setglobalzoommode(newzoommode) + if self.globalzoommode ~= newzoommode then + self.globalzoommode = newzoommode + self:goto(self.pageno) + end +end + +-- adjust zoom state and trigger re-rendering +function UniReader:setglobalzoom(zoom) + if self.globalzoom ~= zoom then + self.globalzoommode = self.ZOOM_BY_VALUE + self.globalzoom = zoom + self:goto(self.pageno) + end +end + +function UniReader:setrotate(rotate) + self.globalrotate = rotate + self:goto(self.pageno) +end + +function UniReader:showTOC() + toc = self.doc:getTOC() + local menu_items = {} + -- build menu items + for _k,_v in ipairs(toc) do + table.insert(menu_items, + (" "):rep(_v.depth-1).._v.title) + end + toc_menu = SelectMenu:new{ + menu_title = "Table of Contents", + item_array = menu_items, + no_item_msg = "This document does not have a Table of Contents.", + } + item_no = toc_menu:choose(0, fb.bb:getHeight()) + if item_no then + self:goto(toc[item_no].page) + else + self:goto(self.pageno) + end +end + +function UniReader:showJumpStack() + local menu_items = {} + for _k,_v in ipairs(self.jump_stack) do + table.insert(menu_items, + _v.datetime.." -> Page ".._v.page) + end + jump_menu = SelectMenu:new{ + menu_title = "Jump Keeper (current page: "..self.pageno..")", + item_array = menu_items, + no_item_msg = "No jump history.", + } + item_no = jump_menu:choose(0, fb.bb:getHeight()) + if item_no then + local jump_item = self.jump_stack[item_no] + self:goto(jump_item.page) + else + self:goto(self.pageno) + end +end + + +-- wait for input and handle it +function UniReader:inputloop() + local keep_running = true + while 1 do + local ev = input.waitForEvent() + ev.code = adjustKeyEvents(ev) + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + local secs, usecs = util.gettime() + if ev.code == KEY_PGFWD or ev.code == KEY_LPGFWD then + if Keys.shiftmode then + self:setglobalzoom(self.globalzoom+self.globalzoom_orig*0.2) + elseif Keys.altmode then + self:setglobalzoom(self.globalzoom+self.globalzoom_orig*0.1) + else + if self.pan_by_page then + self.offset_x = self.pan_x + self.offset_y = self.pan_y + end + self:goto(self.pageno + 1) + end + elseif ev.code == KEY_PGBCK or ev.code == KEY_LPGBCK then + if Keys.shiftmode then + self:setglobalzoom(self.globalzoom-self.globalzoom_orig*0.2) + elseif Keys.altmode then + self:setglobalzoom(self.globalzoom-self.globalzoom_orig*0.1) + else + if self.pan_by_page then + self.offset_x = self.pan_x + self.offset_y = self.pan_y + end + self:goto(self.pageno - 1) + end + elseif ev.code == KEY_BACK then + if Keys.altmode then + -- altmode, exit reader + break + else + -- not altmode, back to last jump + if #self.jump_stack ~= 0 then + self:goto(self.jump_stack[1].page) + end + end + elseif ev.code == KEY_VPLUS then + self:modify_gamma( 1.25 ) + elseif ev.code == KEY_VMINUS then + self:modify_gamma( 0.8 ) + elseif ev.code == KEY_A then + if Keys.shiftmode then + self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT) + else + self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE) + end + elseif ev.code == KEY_S then + if Keys.shiftmode then + self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_WIDTH) + else + self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE_WIDTH) + end + elseif ev.code == KEY_D then + if Keys.shiftmode then + self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HEIGHT) + else + self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE_HEIGHT) + end + elseif ev.code == KEY_F then + self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH) + elseif ev.code == KEY_T then + self:showTOC() + elseif ev.code == KEY_B then + if Keys.shiftmode then + self:add_jump(self.pageno) + else + self:showJumpStack() + end + elseif ev.code == KEY_J then + self:setrotate( self.globalrotate + 10 ) + elseif ev.code == KEY_K then + self:setrotate( self.globalrotate - 10 ) + elseif ev.code == KEY_HOME then + -- signal quit + keep_running = false + break + end + + if self.globalzoommode == self.ZOOM_BY_VALUE then + local x + local y + + if Keys.shiftmode then -- shift always moves in small steps + x = self.shift_x / 2 + y = self.shift_y / 2 + elseif Keys.altmode then + x = self.shift_x / 5 + y = self.shift_y / 5 + elseif self.pan_by_page then + x = width; + y = height - self.pan_margin; -- overlap for lines which didn't fit + else + x = self.shift_x + y = self.shift_y + end + + print("offset "..self.offset_x.."*"..self.offset_x.." shift "..x.."*"..y.." globalzoom="..self.globalzoom) + local old_offset_x = self.offset_x + local old_offset_y = self.offset_y + + if ev.code == KEY_FW_LEFT then + self.offset_x = self.offset_x + x + if self.offset_x > 0 then + self.offset_x = 0 + if self.pan_by_page and self.pageno > 1 then + self.offset_x = self.pan_x + self.offset_y = self.min_offset_y -- bottom + self:goto(self.pageno - 1) + end + end + if self.pan_by_page then + self.offset_y = self.min_offset_y + end + elseif ev.code == KEY_FW_RIGHT then + print("# FW RIGHT "..self.offset_x.." - "..x.." < "..self.min_offset_x); + self.offset_x = self.offset_x - x + if self.offset_x < self.min_offset_x then + self.offset_x = self.min_offset_x + if self.pan_by_page and self.pageno < self.doc:getPages() then + self.offset_x = self.pan_x + self.offset_y = self.pan_y + self:goto(self.pageno + 1) + end + end + if self.pan_by_page then + self.offset_y = self.pan_y + end + elseif ev.code == KEY_FW_UP then + self.offset_y = self.offset_y + y + if self.offset_y > 0 then + self.offset_y = 0 + end + elseif ev.code == KEY_FW_DOWN then + self.offset_y = self.offset_y - y + if self.offset_y < self.min_offset_y then + self.offset_y = self.min_offset_y + end + elseif ev.code == KEY_FW_PRESS then + if Keys.shiftmode then + if self.pan_by_page then + self.offset_x = self.pan_x + self.offset_y = self.pan_y + else + self.offset_x = 0 + self.offset_y = 0 + end + else + self.pan_by_page = not self.pan_by_page + if self.pan_by_page then + self.pan_x = self.offset_x + self.pan_y = self.offset_y + end + end + end + if old_offset_x ~= self.offset_x + or old_offset_y ~= self.offset_y then + self:goto(self.pageno) + end + end + + local nsecs, nusecs = util.gettime() + local dur = (nsecs - secs) * 1000000 + nusecs - usecs + print("E: T="..ev.type.." V="..ev.value.." C="..ev.code.." DUR="..dur) + end + end + + self:clearcache() + if self.doc ~= nil then + self.doc:close() + end + if self.settings ~= nil then + self.settings:savesetting("last_page", self.pageno) + self.settings:savesetting("gamma", self.globalgamma) + self.settings:close() + end + + return keep_running +end + + + + + + + + + + + + + +