diff --git a/.gitignore b/.gitignore index b5e821476..b15d2a355 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,9 @@ lua-* lsqlite3* sqlite-amalgamation* mupdf/ +djvulibre/ luafilesystem/ +.reader.kpdfview.lua kpdfview djvulibre* diff --git a/Makefile b/Makefile index f916d6bc6..31ec8a8cf 100644 --- a/Makefile +++ b/Makefile @@ -2,12 +2,10 @@ LUADIR=lua MUPDFDIR=mupdf -DJVUDIR=djvulibre MUPDFTARGET=build/debug MUPDFLIBDIR=$(MUPDFDIR)/$(MUPDFTARGET) +DJVUDIR=djvulibre -SQLITE3DIR=sqlite-amalgamation-3070900 -LSQLITE3DIR=lsqlite3_svn08 FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.8 LFSDIR=luafilesystem @@ -22,7 +20,10 @@ endif HOSTCC:=gcc HOSTCXX:=g++ -CFLAGS:=-O0 -g +CFLAGS:=-O3 +ARM_CFLAGS:=-march=armv6 +# use this for debugging: +#CFLAGS:=-O0 -g # you can configure an emulation for the (eink) framebuffer here. # the application won't use the framebuffer (and the special e-ink ioctls) @@ -38,6 +39,8 @@ ifdef EMULATE_READER -DEMULATE_READER_W=$(EMULATE_READER_W) \ -DEMULATE_READER_H=$(EMULATE_READER_H) \ EMU_LDFLAGS?=$(shell sdl-config --libs) +else + CFLAGS+= $(ARM_CFLAGS) endif # standard includes @@ -57,28 +60,23 @@ THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \ $(MUPDFLIBDIR)/libjbig2dec.a \ $(MUPDFLIBDIR)/libz.a -# comment this out to build without sqlite3 -SQLITE3OBJS := lsqlite3.o sqlite3.o -SQLITE3LDFLAGS := -lpthread - LUALIB := $(LUADIR)/src/liblua.a -kpdfview: kpdfview.o einkfb.o pdf.o djvu.o blitbuffer.o input.o util.o ft.o $(SQLITE3OBJS) lfs.o $(MUPDFLIBS) $(DJVULIBS) $(THIRDPARTYLIBS) $(LUALIB) - $(CC) -lm -ldl -lstdc++ $(EMU_LDFLAGS) $(SQLITE3LDFLAGS) \ +kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o input.o util.o ft.o lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) $(DJVULIBS) djvu.o + $(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) -lstdc++ \ kpdfview.o \ einkfb.o \ pdf.o \ - djvu.o \ blitbuffer.o \ input.o \ util.o \ ft.o \ - $(SQLITE3OBJS) \ lfs.o \ $(MUPDFLIBS) \ - $(DJVULIBS) \ $(THIRDPARTYLIBS) \ $(LUALIB) \ + djvu.o \ + $(DJVULIBS) \ -o kpdfview einkfb.o input.o: %.o: %.c @@ -93,27 +91,17 @@ kpdfview.o pdf.o blitbuffer.o util.o: %.o: %.c djvu.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ -sqlite3.o: $(SQLITE3DIR)/sqlite3.c - $(CC) -c $(CFLAGS) $(SQLITE3DIR)/sqlite3.c -o $@ - -lsqlite3.o: $(LSQLITE3DIR)/lsqlite3.c - $(CC) -c $(CFLAGS) -I$(LUADIR)/src -I$(SQLITE3DIR) $(LSQLITE3DIR)/lsqlite3.c -o $@ - lfs.o: $(LFSDIR)/src/lfs.c $(CC) -c $(CFLAGS) -I$(LUADIR)/src -I$(LFSDIR)/src $(LFSDIR)/src/lfs.c -o $@ fetchthirdparty: -rm -Rf mupdf -rm -Rf lua lua-5.1.4* - -rm -Rf lsqlite3_svn08* - -rm -Rf sqlite-amalgamation-3070900* -rm -Rf luafilesystem* -rm -Rf $(DJVUDIR) git clone git://git.ghostscript.com/mupdf.git ( cd mupdf ; wget http://www.mupdf.com/download/mupdf-thirdparty.zip && unzip mupdf-thirdparty.zip ) wget http://www.lua.org/ftp/lua-5.1.4.tar.gz && tar xvzf lua-5.1.4.tar.gz && ln -s lua-5.1.4 lua - wget "http://lua.sqlite.org/index.cgi/zip/lsqlite3_svn08.zip?uuid=svn_8" && unzip "lsqlite3_svn08.zip?uuid=svn_8" - wget "http://sqlite.org/sqlite-amalgamation-3070900.zip" && unzip sqlite-amalgamation-3070900.zip git clone https://github.com/keplerproject/luafilesystem.git git clone git://djvu.git.sourceforge.net/gitroot/djvu/djvulibre.git diff --git a/djvu.c b/djvu.c index f7b244755..d3186cc9a 100644 --- a/djvu.c +++ b/djvu.c @@ -1,5 +1,5 @@ /* - KindlePDFViewer: MuPDF abstraction for Lua + KindlePDFViewer: DjvuLibre abstraction for Lua Copyright (C) 2011 Hans-Werner Hilse This program is free software: you can redistribute it and/or modify @@ -117,6 +117,7 @@ static int closeDocument(lua_State *L) { doc->context = NULL; } + return 0; } static int getNumberOfPages(lua_State *L) { @@ -125,6 +126,18 @@ static int getNumberOfPages(lua_State *L) { return 1; } +/* not supported yet, so return empty table */ +static int getTableOfContent(lua_State *L) { + /*int count = 1;*/ + + DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); + /*ol = djvu_load_outline(doc->doc_ref);*/ + + lua_newtable(L); + /*walkTableOfContent(L, ol, &count, 0);*/ + return 1; +} + static int newDrawContext(lua_State *L) { int rotate = luaL_optint(L, 1, 0); double zoom = luaL_optnumber(L, 2, (double) 1.0); @@ -238,34 +251,17 @@ static int getPageSize(lua_State *L) { return 2; } -/*static int getUsedBBox(lua_State *L) {*/ - /*fz_bbox result;*/ - /*fz_matrix ctm;*/ - /*fz_device *dev;*/ - /*DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage");*/ - - /*[> returned BBox is in centi-point (n * 0.01 pt) <]*/ - /*ctm = fz_scale(100, 100);*/ - /*ctm = fz_concat(ctm, fz_rotate(page->page->rotate));*/ - - /*fz_try(page->doc->context) {*/ - /*dev = fz_new_bbox_device(page->doc->context, &result);*/ - /*pdf_run_page(page->doc->xref, page->page, dev, ctm, NULL);*/ - /*}*/ - /*fz_always(page->doc->context) {*/ - /*fz_free_device(dev);*/ - /*}*/ - /*fz_catch(page->doc->context) {*/ - /*return luaL_error(L, "cannot calculate bbox for page");*/ - /*}*/ - - /*lua_pushnumber(L, ((double)result.x0)/100);*/ - /*lua_pushnumber(L, ((double)result.y0)/100);*/ - /*lua_pushnumber(L, ((double)result.x1)/100);*/ - /*lua_pushnumber(L, ((double)result.y1)/100);*/ - - /*return 4;*/ -/*}*/ +/* unsupported so fake it */ +static int getUsedBBox(lua_State *L) { + DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); + + lua_pushnumber(L, (double)0.01); + lua_pushnumber(L, (double)0.01); + lua_pushnumber(L, (double)-0.01); + lua_pushnumber(L, (double)-0.01); + + return 4; +} static int closePage(lua_State *L) { DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); @@ -376,7 +372,7 @@ static const struct luaL_reg djvu_func[] = { static const struct luaL_reg djvudocument_meth[] = { {"openPage", openPage}, {"getPages", getNumberOfPages}, - /*{"getTOC", getTableOfContent},*/ + {"getTOC", getTableOfContent}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} @@ -384,7 +380,7 @@ static const struct luaL_reg djvudocument_meth[] = { static const struct luaL_reg djvupage_meth[] = { {"getSize", getPageSize}, - /*{"getUsedBBox", getUsedBBox},*/ + {"getUsedBBox", getUsedBBox}, {"close", closePage}, {"__gc", closePage}, {"draw", drawPage}, diff --git a/djvu.h b/djvu.h index 0aeb7dec5..00508d973 100644 --- a/djvu.h +++ b/djvu.h @@ -1,5 +1,5 @@ /* - KindlePDFViewer: MuPDF abstraction for Lua + KindlePDFViewer: DjvuLibre abstraction for Lua Copyright (C) 2011 Hans-Werner Hilse This program is free software: you can redistribute it and/or modify @@ -22,7 +22,7 @@ #include #include -int luaopen_pdf(lua_State *L); +int luaopen_djvu(lua_State *L); #define True 1 #endif diff --git a/djvureader.lua b/djvureader.lua index 0e64c1081..e929f765d 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -1,70 +1,19 @@ require "unireader" -DJVUReader = UniReader:new{} +DJVUReader = UniReader:new{ + newDC = function() + print("djvu.newDC") + return djvu.newDC() + end, +} function DJVUReader:init() - self.nulldc = djvu.newDC() + self.nulldc = self.newDC() end -- open a DJVU file and its settings store +-- DJVU does not support password yet 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 = djvu.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 + return self:loadSettings(filename) end diff --git a/filechooser.lua b/filechooser.lua index d9b4e7f2c..c0ad74e30 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -65,7 +65,7 @@ function FileChooser:readdir() if lfs.attributes(self.path.."/"..f, "mode") == "directory" and f ~= "." and not (f==".." and self.path=="/") and not string.match(f, "^%.[^.]") then --print(self.path.." -> adding: '"..f.."'") table.insert(self.dirs, f) - elseif string.match(f, ".+%.[pP][dD][fF]$") or string.match(f, ".+%.[dD][jJ][vV][uU]$")then + elseif string.match(f, ".+%.[pP][dD][fF]$") or string.match(f, ".+%.[dD][jJ][vV][uU]$") then table.insert(self.files, f) end end diff --git a/keys.lua b/keys.lua index 3c4c25568..0ad6c2a81 100644 --- a/keys.lua +++ b/keys.lua @@ -113,6 +113,7 @@ end function set_emu_keycodes() KEY_PGFWD = 117 KEY_PGBCK = 112 + KEY_HOME = 110 -- home KEY_BACK = 22 -- backspace KEY_DEL = 119 -- Delete KEY_MENU = 67 -- F1 diff --git a/kpdfview.c b/kpdfview.c index ab28e653d..9ce45652c 100644 --- a/kpdfview.c +++ b/kpdfview.c @@ -32,9 +32,6 @@ #include "lfs.h" -/* forward declaration for luasqlite3: */ -LUALIB_API int luaopen_lsqlite3(lua_State *L); - lua_State *L; int main(int argc, char **argv) { @@ -58,7 +55,6 @@ int main(int argc, char **argv) { luaopen_util(L); luaopen_ft(L); - luaopen_lsqlite3(L); luaopen_lfs(L); lua_newtable(L); diff --git a/pdf.c b/pdf.c index c375f93e1..e079c54b6 100644 --- a/pdf.c +++ b/pdf.c @@ -79,6 +79,7 @@ static int closeDocument(lua_State *L) { fz_free_context(doc->context); doc->context = NULL; } + return 0; } static int getNumberOfPages(lua_State *L) { @@ -105,16 +106,6 @@ static int walkTableOfContent(lua_State *L, fz_outline* ol, int *count, int dept lua_settable(L, -3); lua_pushstring(L, "title"); - /* workaround for misplaced carriage ret in toc entry */ - int i = 0; - while (ol->title[i]) { - if (ol->title[i] == 0x0d) { - ol->title[i] = ' '; - } - /*printf("%x|", ol->title[i]);*/ - i++; - } - lua_pushstring(L, ol->title); lua_settable(L, -3); diff --git a/pdfreader.lua b/pdfreader.lua index e965094e0..5bd12aa73 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -1,113 +1,18 @@ require "unireader" -PDFReader = UniReader:new{} - +PDFReader = UniReader:new{ + newDC = function() + print("pdf.newDC") + return pdf.newDC() + end, +} function PDFReader:init() - self.nulldc = pdf.newDC() + self.nulldc = self.newDC(); end -- open a PDF file and its settings store function PDFReader:open(filename, password) self.doc = pdf.openDocument(filename, password or "") - 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 PDFReader:setzoom(page) - local dc = pdf.newDC() - local pwidth, pheight = page:getSize(self.nulldc) - - 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 - local x0, y0, x1, y1 = page:getUsedBBox() - 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 - local x0, y0, x1, y1 = page:getUsedBBox() - 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 - local x0, y0, x1, y1 = page:getUsedBBox() - 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 - local x0, y0, x1, y1 = page:getUsedBBox() - 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) - -- 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 + return self:loadSettings(filename) end - diff --git a/reader.lua b/reader.lua index 1c2b1fee8..dba4617cd 100755 --- a/reader.lua +++ b/reader.lua @@ -38,15 +38,15 @@ function openFile(filename) if DJVUReader:open(filename) then page_num = DJVUReader.settings:readsetting("last_page") or 1 DJVUReader:goto(tonumber(page_num)) - DJVUReader:inputloop() reader_settings:savesetting("lastfile", filename) + return 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() reader_settings:savesetting("lastfile", filename) + return PDFReader:inputloop() end end end @@ -124,19 +124,19 @@ PDFReader:init() DJVUReader:init() -- display directory or open file -local patharg = ARGV[optind] or reader_settings:readsetting("lastfile") -if patharg and lfs.attributes(patharg, "mode") == "directory" then +local patharg = reader_settings:readsetting("lastfile") +if ARGV[optind] and lfs.attributes(ARGV[optind], "mode") == "directory" then local running = true - FileChooser:setPath(patharg) + FileChooser:setPath(ARGV[optind]) while running do local file = FileChooser:choose(0,height) if file ~= nil then - openFile(file) + running = openFile(file) else running = false end end -elseif patharg then +elseif patharg and lfs.attributes(patharg, "mode") == "file" then openFile(patharg, optarg["p"]) else return showusage() diff --git a/settings.lua b/settings.lua index b7a446202..c324b8975 100644 --- a/settings.lua +++ b/settings.lua @@ -1,38 +1,66 @@ DocSettings = {} function DocSettings:open(docfile) - local new = {} - new.docdb, errno, errstr = sqlite3.open(docfile..".kpdfview") - if new.docdb ~= nil then - new.docdb:exec("CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT);") - new.stmt_readsetting = new.docdb:prepare("SELECT value FROM settings WHERE key = ?;") - new.stmt_savesetting = new.docdb:prepare("INSERT OR REPLACE INTO settings (key, value) VALUES (?, ?);") + local new = { file = docfile..".kpdfview.lua", data = {} } + local ok, stored = pcall(dofile,new.file) + if ok then + new.data = stored end return setmetatable(new, { __index = DocSettings}) end function DocSettings:readsetting(key) - if self.docdb ~= nil then - self.stmt_readsetting:reset() - self.stmt_readsetting:bind_values(key) - local result = self.stmt_readsetting:step() - if result == sqlite3.ROW then - return self.stmt_readsetting:get_value(0) + return self.data[key] +end + +function DocSettings:savesetting(key, value) + self.data[key] = value +end + +-- simple serialization function, won't do uservalues, functions, loops +function DocSettings:_serialize(what, outt, indent) + if type(what) == "table" then + local didrun = false + table.insert(outt, "{") + for k, v in pairs(what) do + if didrun then + table.insert(outt, ",") + end + table.insert(outt, "\n") + table.insert(outt, string.rep("\t", indent+1)) + table.insert(outt, "[") + self:_serialize(k, outt, indent+1) + table.insert(outt, "] = ") + self:_serialize(v, outt, indent+1) + didrun = true end + if didrun then + table.insert(outt, "\n") + table.insert(outt, string.rep("\t", indent)) + end + table.insert(outt, "}") + elseif type(what) == "string" then + table.insert(outt, string.format("%q", what)) + elseif type(what) == "number" or type(what) == "boolean" then + table.insert(outt, tostring(what)) end end -function DocSettings:savesetting(key, value) - if self.docdb ~= nil then - self.stmt_savesetting:reset() - self.stmt_savesetting:bind_values(key, value) - self.stmt_savesetting:step() +function DocSettings:flush() + -- write a serialized version of the data table + if not self.file then + return + end + local f_out = io.open(self.file, "w") + if f_out ~= nil then + local out = {"-- we can read Lua syntax here!\nreturn "} + self:_serialize(self.data, out, 0) + table.insert(out, "\n") + f_out:write(table.concat(out)) + f_out:close() end end function DocSettings:close() - if self.docdb ~= nil then - self.docdb:close() - self.docdb = nil - end + self:flush() end diff --git a/unireader.lua b/unireader.lua index 80ac95281..3d0e21e03 100644 --- a/unireader.lua +++ b/unireader.lua @@ -11,7 +11,8 @@ UniReader = { ZOOM_FIT_TO_CONTENT = -4, ZOOM_FIT_TO_CONTENT_WIDTH = -5, ZOOM_FIT_TO_CONTENT_HEIGHT = -6, - ZOOM_FIT_TO_CONTENT_HALF_WIDTH = -7, + ZOOM_FIT_TO_CONTENT_HALF_WIDTH_MARGIN = -7, + ZOOM_FIT_TO_CONTENT_HALF_WIDTH = -8, GAMMA_NO_GAMMA = 1.0, @@ -43,16 +44,18 @@ UniReader = { 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, + pan_margin = 20, -- horizontal margin for two-column zoom + pan_overlap_vertical = 30, -- 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 = pdf.newDC(), - nulldc = nil, -- you have to initialize it in specific reader + nulldc = nil, -- tile cache configuration: cache_max_memsize = 1024*1024*5, -- 5MB tile cache @@ -62,6 +65,7 @@ UniReader = { cache_current_memsize = 0, cache = {}, jump_stack = {}, + toc = nil, } function UniReader:new(o) @@ -71,10 +75,46 @@ function UniReader:new(o) return o end +--[[ + For a new specific reader, + you must always overwrite following two methods: + + * self:init() + * self:open() + + overwrite other methods if needed. +--]] function UniReader:init() print("empty initialization method!") end +-- open a file and its settings store +-- tips: you can use self:loadSettings in open() method. +function UniReader:open(filename, password) + return false +end + + + +--[ following are default methods ]-- + +function UniReader:loadSettings(filename) + if self.doc ~= nil then + self.settings = DocSettings:open(filename) + + local gamma = self.settings:readsetting("gamma") + if gamma then + self.globalgamma = gamma + end + + local jumpstack = self.settings:readsetting("jumpstack") + self.jump_stack = jumpstack or {} + + return true + end + return false +end + -- guarantee that we have enough memory in cache function UniReader:cacheclaim(size) if(size > self.cache_max_memsize) then @@ -135,14 +175,113 @@ function UniReader:clearcache() 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) - return nil + 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 + self.pan_by_page = false + 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 + self.pan_by_page = false + 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 + self.pan_by_page = false + 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 + or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH_MARGIN then + local margin = self.pan_margin + if self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH then margin = 0 end + self.globalzoom = width / (x1 - x0 + margin) + self.offset_x = -1 * x0 * self.globalzoom * 2 + margin + self.globalzoom = height / (y1 - y0) + self.offset_y = -1 * y0 * self.globalzoom * 2 + margin + self.globalzoom = width / (x1 - x0 + 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) + 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 + + 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 @@ -166,46 +305,74 @@ function UniReader:show(no) self.slot_visible = slot; end -function UniReader:add_jump(pageno) +--[[ + @ pageno is the page you want to add to jump_stack +--]] +function UniReader:add_jump(pageno, notes) local jump_item = nil - -- add current page to jump_stack if no in + local notes_to_add = notes + if not notes_to_add then + -- no notes given, auto generate from TOC entry + notes_to_add = self:getTOCTitleByPage(self.pageno) + if notes_to_add ~= "" then + notes_to_add = "in "..notes_to_add + end + end + -- move pageno page to jump_stack top if already 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) + -- if original notes is not empty, probably defined by users, + -- we use the original notes to overwrite auto generated notes + -- from TOC entry + if jump_item.notes ~= "" then + notes_to_add = jump_item.notes + end + jump_item.notes = notes or notes_to_add + break end end - -- create a new one if not found + -- create a new one if page not found in stack if not jump_item then jump_item = { page = pageno, datetime = os.date("%Y-%m-%d %H:%M:%S"), + notes = notes_to_add, } end - -- insert at the start + + -- insert item 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 +function UniReader:del_jump(pageno) + for _t,_v in ipairs(self.jump_stack) do + if _v.page == pageno then + table.remove(self.jump_stack, _t) + end + 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 + -- for jump_stack, distinguish jump from normal page turn 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 @@ -245,13 +412,44 @@ function UniReader:setrotate(rotate) self:goto(self.pageno) end +function UniReader:cleanUpTOCTitle(title) + return title:gsub("\13", "") +end + +function UniReader:fillTOC() + self.toc = self.doc:getTOC() +end + +function UniReader:getTOCTitleByPage(pageno) + if not self.toc then + -- build toc when needed. + self:fillTOC() + end + + for _k,_v in ipairs(self.toc) do + if _v.page >= pageno then + return self:cleanUpTOCTitle(_v.title) + end + end + return "" +end + function UniReader:showTOC() - toc = self.doc:getTOC() + if not self.toc then + -- build toc when needed. + self:fillTOC() + end local menu_items = {} + local filtered_toc = {} + local curr_page = -1 -- build menu items - for _k,_v in ipairs(toc) do - table.insert(menu_items, - (" "):rep(_v.depth-1).._v.title) + for _k,_v in ipairs(self.toc) do + if(_v.page >= curr_page) then + table.insert(menu_items, + (" "):rep(_v.depth-1)..self:cleanUpTOCTitle(_v.title)) + table.insert(filtered_toc,_v.page) + curr_page = _v.page + end end toc_menu = SelectMenu:new{ menu_title = "Table of Contents", @@ -260,7 +458,7 @@ function UniReader:showTOC() } item_no = toc_menu:choose(0, fb.bb:getHeight()) if item_no then - self:goto(toc[item_no].page) + self:goto(filtered_toc[item_no]) else self:goto(self.pageno) end @@ -270,7 +468,7 @@ function UniReader:showJumpStack() local menu_items = {} for _k,_v in ipairs(self.jump_stack) do table.insert(menu_items, - _v.datetime.." -> Page ".._v.page) + _v.datetime.." -> Page ".._v.page.." ".._v.notes) end jump_menu = SelectMenu:new{ menu_title = "Jump Keeper (current page: "..self.pageno..")", @@ -289,6 +487,7 @@ 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) @@ -321,16 +520,7 @@ function UniReader:inputloop() elseif ev.code == KEY_BACK then if Keys.altmode then -- altmode, exit reader - 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 + break else -- not altmode, back to last jump if #self.jump_stack ~= 0 then @@ -360,7 +550,11 @@ function UniReader:inputloop() self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE_HEIGHT) end elseif ev.code == KEY_F then - self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH) + if Keys.shiftmode then + self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH) + else + self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH_MARGIN) + end elseif ev.code == KEY_T then self:showTOC() elseif ev.code == KEY_B then @@ -373,6 +567,10 @@ function UniReader:inputloop() 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 @@ -387,7 +585,7 @@ function UniReader:inputloop() 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 + y = height - self.pan_overlap_vertical; -- overlap for lines which didn't fit else x = self.shift_x y = self.shift_y @@ -398,30 +596,32 @@ function UniReader:inputloop() local old_offset_y = self.offset_y if ev.code == KEY_FW_LEFT then + print("# KEY_FW_LEFT "..self.offset_x.." + "..x.." > 0"); 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 + if self.pan_by_page then + if self.offset_x > 0 and self.pageno > 1 then self.offset_x = self.pan_x self.offset_y = self.min_offset_y -- bottom self:goto(self.pageno - 1) + else + self.offset_y = self.min_offset_y end - end - if self.pan_by_page then - self.offset_y = self.min_offset_y + elseif self.offset_x > 0 then + self.offset_x = 0 end elseif ev.code == KEY_FW_RIGHT then + print("# KEY_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 + if self.pan_by_page then + if self.offset_x < self.min_offset_x - self.pan_margin and self.pageno < self.doc:getPages() then self.offset_x = self.pan_x self.offset_y = self.pan_y self:goto(self.pageno + 1) + else + self.offset_y = self.pan_y end - end - if self.pan_by_page then - self.offset_y = self.pan_y + elseif self.offset_x < self.min_offset_x then + self.offset_x = self.min_offset_x end elseif ev.code == KEY_FW_UP then self.offset_y = self.offset_y + y @@ -461,6 +661,19 @@ function UniReader:inputloop() print("E: T="..ev.type.." V="..ev.value.." C="..ev.code.." DUR="..dur) end end -end + -- do clean up stuff + self:clearcache() + self.toc = nil + 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:savesetting("jumpstack", self.jump_stack) + self.settings:close() + end + return keep_running +end