diff --git a/Makefile b/Makefile index 5f9de89ee..d07ac5267 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ LUADIR=lua MUPDFDIR=mupdf +DJVUDIR=djvulibre-3.5.24 MUPDFTARGET=build/debug MUPDFLIBDIR=$(MUPDFDIR)/$(MUPDFTARGET) @@ -43,6 +44,7 @@ KPDFREADER_CFLAGS=$(CFLAGS) -I$(LUADIR)/src -I$(MUPDFDIR)/ # for now, all dependencies except for the libc are compiled into the final binary: MUPDFLIBS := $(MUPDFLIBDIR)/libfitz.a +DJVULIBS := $(DJVUDIR)/libdjvulibre.a THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \ $(MUPDFLIBDIR)/libjpeg.a \ $(MUPDFLIBDIR)/libopenjpeg.a \ @@ -55,11 +57,12 @@ SQLITE3LDFLAGS := -lpthread LUALIB := $(LUADIR)/src/liblua.a -kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o input.o util.o ft.o $(SQLITE3OBJS) lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) - $(CC) -lm -ldl $(EMU_LDFLAGS) $(SQLITE3LDFLAGS) \ +kpdfview: kpdfview.o einkfb.o pdf.o djvu.o blitbuffer.o input.o util.o ft.o $(SQLITE3OBJS) lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) + $(CC) -lm -ldl -ldjvulibre $(EMU_LDFLAGS) $(SQLITE3LDFLAGS) \ kpdfview.o \ einkfb.o \ pdf.o \ + djvu.o \ blitbuffer.o \ input.o \ util.o \ @@ -80,6 +83,9 @@ ft.o: %.o: %.c kpdfview.o pdf.o blitbuffer.o util.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(LFSDIR)/src $< -o $@ +djvu.o: %.o: %.c + $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ + sqlite3.o: $(SQLITE3DIR)/sqlite3.c $(CC) -c $(CFLAGS) $(SQLITE3DIR)/sqlite3.c -o $@ @@ -123,6 +129,10 @@ $(MUPDFLIBS) $(THIRDPARTYLIBS): $(MUPDFDIR)/cmapdump.host $(MUPDFDIR)/fontdump.h # build only thirdparty libs, libfitz and pdf utils, which will care for libmupdf.a being built CFLAGS="$(CFLAGS)" make -C mupdf CC="$(CC)" CMAPDUMP=cmapdump.host FONTDUMP=fontdump.host MUPDF= XPS_APPS= +$(DJVULIBS): + #cd $(DJVUDIR) && ./configure --enable-desktopfiles=no + #make -c $() + $(LUALIB): make -C lua/src CC="$(CC)" CFLAGS="$(CFLAGS)" MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-Wl,-E" liblua.a diff --git a/djvu.c b/djvu.c new file mode 100644 index 000000000..86d5e5566 --- /dev/null +++ b/djvu.c @@ -0,0 +1,414 @@ +/* + KindlePDFViewer: MuPDF abstraction for Lua + Copyright (C) 2011 Hans-Werner Hilse + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include + +#include "blitbuffer.h" +#include "djvu.h" + +typedef struct DjvuDocument { + ddjvu_context_t *context; + ddjvu_document_t *doc_ref; + int pages; +} DjvuDocument; + +typedef struct DjvuPage { + int num; + ddjvu_page_t *page; + ddjvu_pageinfo_t info; + DjvuDocument *doc; +} DjvuPage; + +typedef struct DrawContext { + int rotate; + double zoom; + double gamma; + int offset_x; + int offset_y; + ddjvu_format_t *pixelformat; +} DrawContext; + + +static int handle(lua_State *L, ddjvu_context_t *ctx, int wait) +{ + const ddjvu_message_t *msg; + if (!ctx) + return; + if (wait) + msg = ddjvu_message_wait(ctx); + while ((msg = ddjvu_message_peek(ctx))) + { + switch(msg->m_any.tag) + { + case DDJVU_ERROR: + if (msg->m_error.filename) { + return luaL_error(L, "ddjvu: %s\nddjvu: '%s:%d'\n", + msg->m_error.message, msg->m_error.filename, + msg->m_error.lineno); + } else { + return luaL_error(L, "ddjvu: %s\n", msg->m_error.message); + } + default: + break; + } + ddjvu_message_pop(ctx); + } + + return 0; +} + +static int openDocument(lua_State *L) { + const char *filename = luaL_checkstring(L, 1); + /*const char *password = luaL_checkstring(L, 2);*/ + + DjvuDocument *doc = (DjvuDocument*) lua_newuserdata(L, sizeof(DjvuDocument)); + luaL_getmetatable(L, "djvudocument"); + lua_setmetatable(L, -2); + + doc->context = ddjvu_context_create("DJVUReader"); + if (! doc->context) { + return luaL_error(L, "cannot create context."); + } + + doc->doc_ref = ddjvu_document_create_by_filename(doc->context, filename, TRUE); + while (! ddjvu_document_decoding_done(doc->doc_ref)) + handle(L, doc->context, True); + if (! doc->doc_ref) { + return luaL_error(L, "cannot open DJVU file <%s>", filename); + } + + doc->pages = ddjvu_document_get_pagenum(doc->doc_ref); + return 1; +} + +static int closeDocument(lua_State *L) { + DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); + if(doc->doc_ref != NULL) { + ddjvu_document_release(doc->doc_ref); + doc->doc_ref = NULL; + } + if(doc->context != NULL) { + ddjvu_context_release(doc->context); + doc->context = NULL; + } +} + +static int getNumberOfPages(lua_State *L) { + DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); + lua_pushinteger(L, doc->pages); + return 1; +} + +static int newDrawContext(lua_State *L) { + int rotate = luaL_optint(L, 1, 0); + double zoom = luaL_optnumber(L, 2, (double) 1.0); + int offset_x = luaL_optint(L, 3, 0); + int offset_y = luaL_optint(L, 4, 0); + double gamma = luaL_optnumber(L, 5, (double) -1.0); + unsigned int format_mask[4]; + + DrawContext *dc = (DrawContext*) lua_newuserdata(L, sizeof(DrawContext)); + dc->rotate = rotate; + dc->zoom = zoom; + /*dc->offset_x = offset_x;*/ + /*dc->offset_y = offset_y;*/ + dc->gamma = gamma; + + format_mask[0] = 0x00ff0000; + format_mask[1] = 0x0000ff00; + format_mask[2] = 0x000000ff; + format_mask[3] = 0xff000000; + dc->pixelformat = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 4, format_mask); + ddjvu_format_set_row_order(dc->pixelformat, 1); + ddjvu_format_set_y_direction(dc->pixelformat, 1); + /*ddjvu_format_set_ditherbits(dc->pixelformat, 2);*/ + + luaL_getmetatable(L, "drawcontext"); + lua_setmetatable(L, -2); + + return 1; +} + +static int dcSetOffset(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + dc->offset_x = luaL_checkint(L, 2); + dc->offset_y = luaL_checkint(L, 3); + return 0; +} + +static int dcGetOffset(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + lua_pushinteger(L, dc->offset_x); + lua_pushinteger(L, dc->offset_y); + return 2; +} + +static int dcSetRotate(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + dc->rotate = luaL_checkint(L, 2); + return 0; +} + +static int dcSetZoom(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + dc->zoom = luaL_checknumber(L, 2); + return 0; +} + +static int dcGetRotate(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + lua_pushinteger(L, dc->rotate); + return 1; +} + +static int dcGetZoom(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + lua_pushnumber(L, dc->zoom); + return 1; +} + +static int dcSetGamma(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + dc->gamma = luaL_checknumber(L, 2); + return 0; +} + +static int dcGetGamma(lua_State *L) { + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 1, "drawcontext"); + lua_pushnumber(L, dc->gamma); + return 1; +} + +static int openPage(lua_State *L) { + DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); + int pageno = luaL_checkint(L, 2); + + if(pageno < 1 || pageno > doc->pages) { + return luaL_error(L, "cannot open page #%d, out of range (1-%d)", pageno, doc->pages); + } + + DjvuPage *page = (DjvuPage*) lua_newuserdata(L, sizeof(DjvuPage)); + luaL_getmetatable(L, "djvupage"); + lua_setmetatable(L, -2); + + page->page = ddjvu_page_create_by_pageno(doc->doc_ref, pageno - 1); + while (! ddjvu_page_decoding_done(page->page)) + handle(L, doc->context, TRUE); + if(! page->page) { + return luaL_error(L, "cannot open page #%d", pageno); + } + + page->doc = doc; + + /* @TODO:handle failure here */ + ddjvu_document_get_pageinfo(doc->doc_ref, pageno, &(page->info)); + + return 1; +} + +static int getPageSize(lua_State *L) { + /*fz_matrix ctm;*/ + /*fz_rect bbox;*/ + DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + + /*ctm = fz_translate(0, -page->page->mediabox.y1);*/ + /*ctm = fz_concat(ctm, fz_scale(dc->zoom, -dc->zoom));*/ + /*ctm = fz_concat(ctm, fz_rotate(page->page->rotate));*/ + /*ctm = fz_concat(ctm, fz_rotate(dc->rotate));*/ + /*bbox = fz_transform_rect(ctm, page->page->mediabox);*/ + + /*lua_pushnumber(L, bbox.x1-bbox.x0);*/ + /*lua_pushnumber(L, bbox.y1-bbox.y0);*/ + + /*lua_pushnumber(L, page->info.width);*/ + /*lua_pushnumber(L, page->info.height);*/ + + lua_pushnumber(L, ddjvu_page_get_width(page)); + lua_pushnumber(L, ddjvu_page_get_height(page)); + + 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;*/ +/*}*/ + +static int closePage(lua_State *L) { + DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); + /*if(page->page != NULL) {*/ + /*pdf_free_page(page->doc->xref, page->page);*/ + /*page->page = NULL;*/ + /*}*/ + return 0; +} + +static int drawPage(lua_State *L) { + /*fz_pixmap *pix;*/ + /*fz_device *dev;*/ + /*fz_matrix ctm;*/ + /*fz_bbox bbox;*/ + ddjvu_rect_t pagerect, renderrect; + char imagebuffer[600*800*4+1]; + memset(imagebuffer, 0, 600*800*4+1); + + + DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage"); + DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); + BlitBuffer *bb = (BlitBuffer*) luaL_checkudata(L, 3, "blitbuffer"); + /*rect.x0 = luaL_checkint(L, 4);*/ + /*rect.y0 = luaL_checkint(L, 5);*/ + /*rect.x1 = rect.x0 + bb->w;*/ + /*rect.y1 = rect.y0 + bb->h;*/ + + /* render page into rectangle specified by pagerect */ + pagerect.x = luaL_checkint(L, 4); + pagerect.y = luaL_checkint(L, 5); + pagerect.w = bb->w - pagerect.x; + pagerect.h = bb->h - pagerect.y; + + /* copy pixels area from pagerect specified by renderrect */ + renderrect.x = dc->offset_x; + renderrect.y = dc->offset_y; + renderrect.w = bb->w - renderrect.x; + renderrect.h = bb->h - renderrect.y; + + /*fz_clear_pixmap_with_value(page->doc->context, pix, 0xff);*/ + + /*ctm = fz_scale(dc->zoom, dc->zoom);*/ + /*ctm = fz_concat(ctm, fz_rotate(page->page->rotate));*/ + /*ctm = fz_concat(ctm, fz_rotate(dc->rotate));*/ + /*ctm = fz_concat(ctm, fz_translate(dc->offset_x, dc->offset_y));*/ + /*dev = fz_new_draw_device(page->doc->context, pix);*/ + + printf("%d, %d\n", bb->w, bb->h); + ddjvu_page_render(page->page, + DDJVU_FORMAT_RGBMASK32, + &pagerect, + &renderrect, + dc->pixelformat, + (bb->w)*4, + imagebuffer); + + /*if(dc->gamma >= 0.0) {*/ + /*fz_gamma_pixmap(page->doc->context, pix, dc->gamma);*/ + /*}*/ + + uint8_t *bbptr = (uint8_t*)bb->data; + uint16_t *pmptr = (uint16_t*)imagebuffer; + int x, y; + + for(y = 0; y < bb->h; y++) { + for(x = 0; x < (bb->w / 2); x++) { + bbptr[x] = (((pmptr[x*2 + 1] & 0xF0) >> 4) | (pmptr[x*2] & 0xF0)) ^ 0xFF; + } + if(bb->w & 1) { + bbptr[x] = pmptr[x*2] & 0xF0; + } + bbptr += bb->pitch; + pmptr += bb->w; + } + + return 0; +} + +static const struct luaL_reg djvu_func[] = { + {"openDocument", openDocument}, + {"newDC", newDrawContext}, + {NULL, NULL} +}; + +static const struct luaL_reg djvudocument_meth[] = { + {"openPage", openPage}, + {"getPages", getNumberOfPages}, + /*{"getTOC", getTableOfContent},*/ + {"close", closeDocument}, + {"__gc", closeDocument}, + {NULL, NULL} +}; + +static const struct luaL_reg djvupage_meth[] = { + {"getSize", getPageSize}, + /*{"getUsedBBox", getUsedBBox},*/ + {"close", closePage}, + {"__gc", closePage}, + {"draw", drawPage}, + {NULL, NULL} +}; + +static const struct luaL_reg drawcontext_meth[] = { + {"setRotate", dcSetRotate}, + {"getRotate", dcGetRotate}, + {"setZoom", dcSetZoom}, + {"getZoom", dcGetZoom}, + {"setOffset", dcSetOffset}, + {"getOffset", dcGetOffset}, + {"setGamma", dcSetGamma}, + {"getGamma", dcGetGamma}, + {NULL, NULL} +}; + +int luaopen_djvu(lua_State *L) { + luaL_newmetatable(L, "djvudocument"); + lua_pushstring(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); + luaL_register(L, NULL, djvudocument_meth); + lua_pop(L, 1); + + luaL_newmetatable(L, "djvupage"); + lua_pushstring(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); + luaL_register(L, NULL, djvupage_meth); + lua_pop(L, 1); + + luaL_newmetatable(L, "drawcontext"); + lua_pushstring(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); + luaL_register(L, NULL, drawcontext_meth); + lua_pop(L, 1); + + luaL_register(L, "djvu", djvu_func); + return 1; +} diff --git a/djvu.h b/djvu.h new file mode 100644 index 000000000..0aeb7dec5 --- /dev/null +++ b/djvu.h @@ -0,0 +1,28 @@ +/* + KindlePDFViewer: MuPDF abstraction for Lua + Copyright (C) 2011 Hans-Werner Hilse + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef _DJVU_H +#define _DJVU_H + +#include +#include +#include + +int luaopen_pdf(lua_State *L); + +#define True 1 +#endif diff --git a/djvureader.lua b/djvureader.lua new file mode 100644 index 000000000..37ab6dcc0 --- /dev/null +++ b/djvureader.lua @@ -0,0 +1,496 @@ +require "keys" +require "settings" +require "selectmenu" + +DJVUReader = { + -- "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, + + 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 + + -- keep track of input state: + shiftmode = false, -- shift pressed + altmode = false, -- alt pressed + + -- the djvu document: + doc = nil, + -- the document's setting store: + settings = nil, + + -- we will use this one often, so keep it "static": + nulldc = djvu.newDC(), + + -- 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 = {}, +} + +-- guarantee that we have enough memory in cache +function DJVUReader: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 DJVUReader: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 DJVUReader: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 DJVUReader:clearcache() + self.cache = {} + self.cache_current_memsize = 0 +end + +-- open a DJVU file and its settings store +function DJVUReader:open(filename, password) + self.doc = djvu.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 DJVUReader:setzoom(page) + local dc = djvu.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 + 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 + + -- 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 DJVUReader: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 + +-- change current page and cache next page after rendering +function DJVUReader: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 + --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 == self.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 = self.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]] + + 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 DJVUReader: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 DJVUReader:setglobalzoommode(newzoommode) + if self.globalzoommode ~= newzoommode then + self.globalzoommode = newzoommode + self:goto(self.pageno) + end +end + +-- adjust zoom state and trigger re-rendering +function DJVUReader:setglobalzoom(zoom) + if self.globalzoom ~= zoom then + self.globalzoommode = self.ZOOM_BY_VALUE + self.globalzoom = zoom + self:goto(self.pageno) + end +end + +function DJVUReader:setrotate(rotate) + self.globalrotate = rotate + self:goto(self.pageno) +end + +function DJVUReader: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 DJVUReader: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 DJVUReader:inputloop() + while 1 do + local ev = input.waitForEvent() + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + local secs, usecs = util.gettime() + if ev.code == KEY_SHIFT then + self.shiftmode = true + elseif ev.code == KEY_ALT then + self.altmode = true + elseif ev.code == KEY_PGFWD or ev.code == KEY_LPGFWD then + if self.shiftmode then + self:setglobalzoom(self.globalzoom*1.2) + elseif self.altmode then + self:setglobalzoom(self.globalzoom*1.1) + else + self:goto(self.pageno + 1) + end + elseif ev.code == KEY_PGBCK or ev.code == KEY_LPGBCK then + if self.shiftmode then + self:setglobalzoom(self.globalzoom*0.8) + elseif self.altmode then + self:setglobalzoom(self.globalzoom*0.9) + else + self:goto(self.pageno - 1) + end + elseif ev.code == KEY_BACK then + if self.altmode then + -- altmode, exit djvureader + 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 + 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 self.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 self.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 self.shiftmode then + self:setglobalzoommode(self.ZOOM_FIT_TO_CONTENT_HEIGHT) + else + self:setglobalzoommode(self.ZOOM_FIT_TO_PAGE_HEIGHT) + end + elseif ev.code == KEY_T then + self:showTOC() + elseif ev.code == KEY_B then + self:showJumpStack() + elseif ev.code == KEY_J then + self:setrotate( self.globalrotate + 10 ) + elseif ev.code == KEY_K then + self:setrotate( self.globalrotate - 10 ) + end + + if self.globalzoommode == self.ZOOM_BY_VALUE then + local x + local y + + if self.shiftmode then -- shift always moves in small steps + x = self.shift_x / 2 + y = self.shift_y / 2 + elseif self.altmode then + x = self.shift_x / 5 + y = self.shift_y / 5 + elseif self.pan_by_page then + x = self.width - 5; -- small overlap when moving by page + y = self.height - 5; + 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 + end + elseif ev.code == KEY_FW_RIGHT then + self.offset_x = self.offset_x - x + if 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 + 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 self.shiftmode then + self.offset_x = 0 + self.offset_y = 0 + else + self.pan_by_page = not self.pan_by_page + 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) + elseif ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_RELEASE and ev.code == KEY_SHIFT then + print "shift haha" + self.shiftmode = false + elseif ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_RELEASE and ev.code == KEY_ALT then + self.altmode = false + end + end +end + + diff --git a/kpdfview.c b/kpdfview.c index 9e5fa60ad..ab28e653d 100644 --- a/kpdfview.c +++ b/kpdfview.c @@ -53,6 +53,7 @@ int main(int argc, char **argv) { luaopen_blitbuffer(L); luaopen_einkfb(L); luaopen_pdf(L); + luaopen_djvu(L); luaopen_input(L); luaopen_util(L); luaopen_ft(L); diff --git a/pdfreader.lua b/pdfreader.lua index ef9a7ab58..eddf0beaa 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -1,6 +1,5 @@ require "keys" require "settings" ---require "tocmenu" require "selectmenu" PDFReader = { diff --git a/reader.lua b/reader.lua index 62d4dee64..17cb1224d 100755 --- a/reader.lua +++ b/reader.lua @@ -19,6 +19,7 @@ require "alt_getopt" require "pdfreader" +require "djvureader" require "filechooser" require "settings" @@ -52,6 +53,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,33 +93,34 @@ if r_cfont ~=nil then FontChooser.cfont = r_cfont end +DJVUReader:open("/home/dave/documents/code/kindle/djvu/test-djvu/test.djvu") +DJVUReader:goto(1) +DJVUReader:inputloop() -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)) - PDFReader:inputloop() - end - 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() - DJVUReader:open("/home/dave/documents/code/kindle/djvu/test-djvu/test.djvu") - DJVUReader:goto(1) - DJVUReader:inputloop() -end --- save reader settings -reader_settings:savesetting("cfont", FontChooser.cfont) -reader_settings:close() +--[[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)) + --PDFReader:inputloop() + --end + --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() +--end + +---- save reader settings +--reader_settings:savesetting("cfont", FontChooser.cfont) +--reader_settings:close() -input.closeAll() -os.execute('test -e /proc/keypad && echo "send '..KEY_HOME..'" > /proc/keypad ') +--input.closeAll() +--[[os.execute('test -e /proc/keypad && echo "send '..KEY_HOME..'" > /proc/keypad ')]]