Merge remote branch 'hwhw/master'

* hwhw/master: (183 commits)
  show infomessage on document open
  use InfoMessage for empty TOC, jump history and highlights
  copy resources dir on customupdate
  add resource: info icon
  display document open error message
  added infomessage dialog implementation
  bugfix, removed old test constant
  Added widget abstraction framework
  added interface to get blitbuffers from JPEG/PNG files
  fix typo
  added reading of pan_margin settings
  fix full screen refresh command, close #99
  add: sleep and usleep in util module
  fix: add back KEY_FW{LEFT,RIGHT} commands to NumInputBox
  reverted removal of last-doc shortcut, introduced framework restart
  kill our own child process. not quite finished.
  added line spacing setting for crereader
  remove page:getPageText debug dump to improve performance on device
  remove page:getPageText debug dump to improve performance on device
  display crash.log on error
  ...

Conflicts:
	ft.c
	helppage.lua
	inputbox.lua
	rendertext.lua
	rendertext_example.lua
	unireader.lua
pull/2/merge
traycold 12 years ago
commit 02dd9b023f

7
.gitignore vendored

@ -3,10 +3,17 @@ lua
lua-*
.reader.kpdfview.lua
mupdf-thirdparty.zip
djvulibre*
kpdfview
*.o
kindlepdfviewer-*.zip
/.cproject
/.project
/.reader.kpdfview
kpvcrlib/CMakeCache.txt
kpvcrlib/CMakeFiles/
kpvcrlib/cmake_install.cmake
kpvcrlib/Makefile

3
.gitmodules vendored

@ -7,3 +7,6 @@
[submodule "djvulibre"]
path = djvulibre
url = git://djvu.git.sourceforge.net/gitroot/djvu/djvulibre.git
[submodule "kpvcrlib/crengine"]
path = kpvcrlib/crengine
url = git://crengine.git.sourceforge.net/gitroot/crengine/crengine

@ -5,15 +5,21 @@ MUPDFDIR=mupdf
MUPDFTARGET=build/debug
MUPDFLIBDIR=$(MUPDFDIR)/$(MUPDFTARGET)
DJVUDIR=djvulibre
KPVCRLIGDIR=kpvcrlib
CRENGINEDIR=$(KPVCRLIGDIR)/crengine
FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.8
LFSDIR=luafilesystem
# must point to directory with *.ttf fonts for crengine
TTF_FONTS_DIR=$(MUPDFDIR)/fonts
# set this to your ARM cross compiler:
CC:=arm-unknown-linux-gnueabi-gcc
CXX:=arm-unknown-linux-gnueabi-g++
HOST:=arm-unknown-linux-gnueabi
HOST:=arm-none-linux-gnueabi
CC:=$(HOST)-gcc
CXX:=$(HOST)-g++
STRIP:=$(HOST)-strip
ifdef SBOX_UNAME_MACHINE
CC:=gcc
CXX:=g++
@ -21,11 +27,18 @@ endif
HOSTCC:=gcc
HOSTCXX:=g++
CFLAGS:=-O3
CFLAGS:=-O3 $(SYSROOT)
CXXFLAGS:=-O3 $(SYSROOT)
LDFLAGS:= $(SYSROOT)
ARM_CFLAGS:=-march=armv6
# use this for debugging:
#CFLAGS:=-O0 -g
DYNAMICLIBSTDCPP:=-lstdc++
ifdef STATICLIBSTDCPP
DYNAMICLIBSTDCPP:=
endif
# you can configure an emulation for the (eink) framebuffer here.
# the application won't use the framebuffer (and the special e-ink ioctls)
# in that case.
@ -55,16 +68,27 @@ KPDFREADER_CFLAGS=$(CFLAGS) -I$(LUADIR)/src -I$(MUPDFDIR)/
MUPDFLIBS := $(MUPDFLIBDIR)/libfitz.a
DJVULIBS := $(DJVUDIR)/build/libdjvu/.libs/libdjvulibre.a
CRENGINELIBS := $(CRENGINEDIR)/crengine/libcrengine.a \
$(CRENGINEDIR)/thirdparty/chmlib/libchmlib.a \
$(CRENGINEDIR)/thirdparty/libpng/libpng.a \
# we don't support dictionary lookup corrently
#$(CRENGINEDIR)/thirdparty/antiword/libantiword.a
THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \
$(MUPDFLIBDIR)/libjpeg.a \
$(MUPDFLIBDIR)/libopenjpeg.a \
$(MUPDFLIBDIR)/libjbig2dec.a \
$(MUPDFLIBDIR)/libz.a
$(MUPDFLIBDIR)/libopenjpeg.a \
$(MUPDFLIBDIR)/libjbig2dec.a \
$(MUPDFLIBDIR)/libjpeg.a \
$(MUPDFLIBDIR)/libz.a
#@TODO patch crengine to use the latest libjpeg 04.04 2012 (houqp)
#$(MUPDFLIBDIR)/libjpeg.a \
#$(CRENGINEDIR)/thirdparty/libjpeg/libjpeg.a \
LUALIB := $(LUADIR)/src/liblua.a
kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) $(DJVULIBS) djvu.o
$(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) -lstdc++ \
all:kpdfview
kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o mupdfimg.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS)
$(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) $(DYNAMICLIBSTDCPP) \
kpdfview.o \
einkfb.o \
pdf.o \
@ -74,44 +98,67 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft
util.o \
ft.o \
lfs.o \
mupdfimg.o \
$(MUPDFLIBS) \
$(THIRDPARTYLIBS) \
$(LUALIB) \
djvu.o \
$(DJVULIBS) \
cre.o \
$(CRENGINELIBS) \
$(STATICLIBSTDCPP) \
-o kpdfview
einkfb.o input.o: %.o: %.c
$(CC) -c $(KPDFREADER_CFLAGS) $(EMU_CFLAGS) $< -o $@
slider_watcher: slider_watcher.c
$(CC) $(CFLAGS) $< -o $@
ft.o: %.o: %.c
$(CC) -c $(KPDFREADER_CFLAGS) -I$(FREETYPEDIR)/include -I$(MUPDFDIR)/fitz $< -o $@
kpdfview.o pdf.o blitbuffer.o util.o drawcontext.o: %.o: %.c
$(CC) -c $(KPDFREADER_CFLAGS) -I$(LFSDIR)/src $< -o $@
kpdfview.o pdf.o blitbuffer.o util.o drawcontext.o einkfb.o input.o mupdfimg.o: %.o: %.c
$(CC) -c $(KPDFREADER_CFLAGS) $(EMU_CFLAGS) -I$(LFSDIR)/src $< -o $@
djvu.o: %.o: %.c
$(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@
cre.o: %.o: %.cpp
$(CC) -c -I$(CRENGINEDIR)/crengine/include/ -Ilua/src $< -o $@ -lstdc++
lfs.o: $(LFSDIR)/src/lfs.c
$(CC) -c $(CFLAGS) -I$(LUADIR)/src -I$(LFSDIR)/src $(LFSDIR)/src/lfs.c -o $@
fetchthirdparty:
-rm -Rf lua lua-5.1.4*
-rm -Rf lua lua-5.1.4
-rm -Rf mupdf/thirdparty
test -d mupdf && (cd mupdf; git checkout .)
git submodule init
git submodule update
ln -sf kpvcrlib/crengine/cr3gui/data data
test -d fonts || ln -sf $(TTF_FONTS_DIR) fonts
# CREngine patch: disable fontconfig
grep USE_FONTCONFIG $(CRENGINEDIR)/crengine/include/crsetup.h && grep -v USE_FONTCONFIG $(CRENGINEDIR)/crengine/include/crsetup.h > /tmp/new && mv /tmp/new $(CRENGINEDIR)/crengine/include/crsetup.h
test -f mupdf-thirdparty.zip || wget http://www.mupdf.com/download/mupdf-thirdparty.zip
unzip mupdf-thirdparty.zip -d mupdf
# dirty patch in MuPDF's thirdparty liby for CREngine
cd mupdf/thirdparty/jpeg-*/ && \
patch -N -p0 < ../../../kpvcrlib/jpeg_compress_struct_size.patch &&\
patch -N -p0 < ../../../kpvcrlib/jpeg_decompress_struct_size.patch
# MuPDF patch: use external fonts
cd mupdf && patch -N -p1 < ../mupdf.patch
test -f lua-5.1.4.tar.gz || 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
clean:
-rm -f *.o kpdfview
-rm -f *.o kpdfview slider_watcher
cleanthirdparty:
make -C $(LUADIR) clean
make -C $(MUPDFDIR) clean
#make -C $(CRENGINEDIR)/thirdparty/antiword clean
make -C $(CRENGINEDIR)/thirdparty/chmlib clean
make -C $(CRENGINEDIR)/thirdparty/libpng clean
make -C $(CRENGINEDIR)/crengine clean
make -C $(KPVCRLIGDIR) clean
-rm -rf $(DJVUDIR)/build
-rm -f $(MUPDFDIR)/fontdump.host
-rm -f $(MUPDFDIR)/cmapdump.host
@ -128,21 +175,26 @@ $(MUPDFDIR)/cmapdump.host:
$(MUPDFLIBS) $(THIRDPARTYLIBS): $(MUPDFDIR)/cmapdump.host $(MUPDFDIR)/fontdump.host
# 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=
CFLAGS="$(CFLAGS) -DNOBUILTINFONT" make -C mupdf CC="$(CC)" CMAPDUMP=cmapdump.host FONTDUMP=fontdump.host MUPDF= XPS_APPS= verbose=1
$(DJVULIBS):
-mkdir $(DJVUDIR)/build
ifdef EMULATE_READER
cd $(DJVUDIR)/build && ../configure --disable-desktopfiles --disable-shared --enable-static
else
cd $(DJVUDIR)/build && ../configure --disable-desktopfiles --disable-shared --enable-static --host=$(HOST)
cd $(DJVUDIR)/build && ../configure --disable-desktopfiles --disable-shared --enable-static --host=$(HOST) --disable-xmltools --disable-desktopfiles
endif
make -C $(DJVUDIR)/build
$(CRENGINELIBS):
cd $(KPVCRLIGDIR) && rm -rf CMakeCache.txt CMakeFiles && \
CFLAGS="$(CFLAGS)" CC="$(CC)" CXX="$(CXX)" cmake . && \
make
$(LUALIB):
make -C lua/src CC="$(CC)" CFLAGS="$(CFLAGS)" MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-Wl,-E" liblua.a
thirdparty: $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) $(DJVULIBS)
thirdparty: $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) $(DJVULIBS) $(CRENGINELIBS)
INSTALL_DIR=kindlepdfviewer
@ -152,11 +204,19 @@ install:
scp launchpad/* root@192.168.2.2:/mnt/us/launchpad/
VERSION?=$(shell git rev-parse --short HEAD)
customupdate: kpdfview
customupdate: all
# ensure that build binary is for ARM
file kpdfview | grep ARM || exit 1
$(STRIP) --strip-unneeded kpdfview
-rm kindlepdfviewer-$(VERSION).zip
rm -Rf $(INSTALL_DIR)
mkdir $(INSTALL_DIR)
cp -p README.TXT COPYING kpdfview *.lua $(INSTALL_DIR)
zip -r kindlepdfviewer-$(VERSION).zip $(INSTALL_DIR) launchpad/
mkdir $(INSTALL_DIR)/data
cp -rpL data/*.css $(INSTALL_DIR)/data
cp -rpL fonts $(INSTALL_DIR)
cp -r resources $(INSTALL_DIR)
mkdir $(INSTALL_DIR)/fonts/host
zip -9 -r kindlepdfviewer-$(VERSION).zip $(INSTALL_DIR) launchpad/
rm -Rf $(INSTALL_DIR)
@echo "copy kindlepdfviewer-$(VERSION).zip to /mnt/us/customupdates and install with shift+shift+I"

@ -366,6 +366,124 @@ static int paintRect(lua_State *L) {
return 0;
}
static int invertRect(lua_State *L) {
BlitBuffer *dst = (BlitBuffer*) luaL_checkudata(L, 1, "blitbuffer");
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
int w = luaL_checkint(L, 4);
int h = luaL_checkint(L, 5);
uint8_t *dstptr;
int cy, cx;
if(w <= 0 || h <= 0 || x >= dst->w || y >= dst->h) {
return 0;
}
if(x + w > dst->w) {
w = dst->w - x;
}
if(y + h > dst->h) {
h = dst->h - y;
}
if(x & 1) {
/* This will invert the leftmost column
* in the case when x is odd. After this,
* x will become even. */
dstptr = (uint8_t*)(dst->data +
y * dst->pitch +
x / 2);
for(cy = 0; cy < h; cy++) {
*dstptr ^= 0x0F;
dstptr += dst->pitch;
}
x++;
w--;
}
dstptr = (uint8_t*)(dst->data +
y * dst->pitch +
x / 2);
for(cy = 0; cy < h; cy++) {
for(cx = 0; cx < w/2; cx++) {
*(dstptr+cx) ^= 0xFF;
}
dstptr += dst->pitch;
}
if(w & 1) {
/* This will invert the rightmost column
* in the case when (w & 1) && !(x & 1) or
* !(w & 1) && (x & 1). */
dstptr = (uint8_t*)(dst->data +
y * dst->pitch +
(x + w) / 2);
for(cy = 0; cy < h; cy++) {
*dstptr ^= 0xF0;
dstptr += dst->pitch;
}
}
return 0;
}
static int dimRect(lua_State *L) {
BlitBuffer *dst = (BlitBuffer*) luaL_checkudata(L, 1, "blitbuffer");
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
int w = luaL_checkint(L, 4);
int h = luaL_checkint(L, 5);
uint8_t *dstptr;
int cy, cx;
if(w <= 0 || h <= 0 || x >= dst->w || y >= dst->h) {
return 0;
}
if(x + w > dst->w) {
w = dst->w - x;
}
if(y + h > dst->h) {
h = dst->h - y;
}
if(x & 1) {
/* This will dimm the leftmost column
* in the case when x is odd. After this,
* x will become even. */
dstptr = (uint8_t*)(dst->data +
y * dst->pitch +
x / 2);
for(cy = 0; cy < h; cy++) {
int px = *dstptr & 0x0F;
*dstptr &= 0xF0 | px >> 1;
dstptr += dst->pitch;
}
x++;
w--;
}
dstptr = (uint8_t*)(dst->data +
y * dst->pitch +
x / 2);
for(cy = 0; cy < h; cy++) {
for(cx = 0; cx < w/2; cx++) {
*(dstptr+cx) =
( *(dstptr+cx) >> 1 ) & 0xF0 |
( *(dstptr+cx) & 0x0F ) >> 1;
}
dstptr += dst->pitch;
}
if(w & 1) {
/* This will dimm the rightmost column
* in the case when (w & 1) && !(x & 1) or
* !(w & 1) && (x & 1). */
dstptr = (uint8_t*)(dst->data +
y * dst->pitch +
(x + w) / 2);
for(cy = 0; cy < h; cy++) {
int px = *dstptr & 0xF0;
*dstptr &= 0x0F | ( px >> 1 & 0xF0 );
dstptr += dst->pitch;
}
}
return 0;
}
static const struct luaL_Reg blitbuffer_func[] = {
{"new", newBlitBuffer},
{NULL, NULL}
@ -378,6 +496,8 @@ static const struct luaL_Reg blitbuffer_meth[] = {
{"addblitFrom", addblitToBuffer},
{"blitFullFrom", blitFullToBuffer},
{"paintRect", paintRect},
{"invertRect", invertRect},
{"dimRect", dimRect},
{"free", freeBlitBuffer},
{"__gc", freeBlitBuffer},
{NULL, NULL}

@ -5,6 +5,7 @@ Keydef = {
modifier = nil,
descr = nil
}
function Keydef:_new(obj)
-- obj definition
obj = obj or {}
@ -13,6 +14,7 @@ function Keydef:_new(obj)
self.__tostring=Keydef.tostring
return obj
end
function Keydef:new(keycode,modifier,descr)
obj = Keydef:_new()
obj.keycode = keycode
@ -20,13 +22,16 @@ function Keydef:new(keycode,modifier,descr)
obj.descr = descr
return obj
end
function Keydef:display()
return (self.modifier or "")..(self.descr or "")
end
function Keydef:tostring()
return ((self.modifier and self.modifier.."+") or "").."["..(self.keycode or "").."]"..(self.descr or "")
end
Command = {
keydef = nil,
keygroup = nil,
@ -34,6 +39,7 @@ Command = {
help = nil,
order = nil
}
function Command:_new(obj)
-- obj definition
obj = obj or {}
@ -42,6 +48,7 @@ function Command:_new(obj)
self.__tostring=Command.tostring
return obj
end
function Command:new(keydef, func, help, keygroup, order)
obj = Command:_new()
obj.keydef = keydef
@ -52,6 +59,7 @@ function Command:new(keydef, func, help, keygroup, order)
--print("creating command: ["..tostring(keydef).."] keygroup:["..(keygroup or "").."] help:"..help)
return obj
end
function Command:tostring()
return tostring(self.keydef)..": "..(self.help or "<no help defined>")
end
@ -61,15 +69,54 @@ Commands = {
map = {},
size = 0
}
function Commands:add(keycode,modifier,keydescr,help,func)
local keydef = Keydef:new(keycode,modifier,keydescr)
self:_addImpl(keydef,help,func)
if type(keycode) == "table" then
for i=1,#keycode,1 do
local keydef = Keydef:new(keycode[i],modifier,keydescr)
self:_addImpl(keydef,help,func)
end
else
local keydef = Keydef:new(keycode,modifier,keydescr)
self:_addImpl(keydef,help,func)
end
end
function Commands:addGroup(keygroup,keys,help,func)
for _k,keydef in pairs(keys) do
self:_addImpl(keydef,help,func,keygroup)
end
end
--@TODO handle MOD_ANY 06.04 2012 (houqp)
function Commands:del(keycode, modifier, keydescr)
local keydef = nil
if not keydescr then
for k,v in pairs(self.map) do
if v.keydef.keycode == keycode
and v.keydef.modifier == modifier then
keydef = k
break
end
end -- EOF for
else
keydef = Keydef:new(keycode, modifier, keydescr)
end -- EOF if
self.map[keydef] = nil
end
function Commands:delGroup(keygroup)
if keygroup then
for k,v in pairs(self.map) do
if v.keygroup == keygroup then
self.map[k] = nil
end
end -- EOF for
end
end
function Commands:_addImpl(keydef,help,func,keygroup)
if keydef.modifier==MOD_ANY then
self:addGroup(keygroup or keydef.descr,{Keydef:new(keydef.keycode,nil), Keydef:new(keydef.keycode,MOD_SHIFT), Keydef:new(keydef.keycode,MOD_ALT)},help,func)
@ -88,25 +135,51 @@ function Commands:_addImpl(keydef,help,func,keygroup)
end
end
end
function Commands:get(keycode,modifier)
return self.map[Keydef:new(keycode, modifier)]
end
function Commands:getByKeydef(keydef)
return self.map[keydef]
end
function Commands:new(obj)
-- obj definition
obj = obj or {}
obj.map = {}
obj.size = 0
setmetatable(obj, self)
self.__index = self
-- payload
local mt = {}
setmetatable(self.map,mt)
mt.__index=function (table, key)
mt.__index = function(table, key)
return rawget(table,(key.modifier or "").."@#@"..(key.keycode or ""))
end
mt.__newindex=function (table, key, value)
mt.__newindex = function(table, key, value)
return rawset(table,(key.modifier or "").."@#@"..(key.keycode or ""),value)
end
-- obj definition
obj = obj or {}
setmetatable(obj, self)
self.__index = self
setmetatable(obj.map, mt)
obj:add(KEY_INTO_SCREEN_SAVER, nil, "Slider",
"toggle screen saver",
function()
Screen:saveCurrentBB()
Screen.kpv_rotation_mode = Screen.cur_rotation_mode
fb:setOrientation(Screen.native_rotation_mode)
--os.execute("killall -cont cvm")
end
)
obj:add(KEY_OUTOF_SCREEN_SAVER, nil, "Slider",
"toggle screen saver",
function()
util.sleep(3)
--os.execute("killall -stop cvm")
fb:setOrientation(Screen.kpv_rotation_mode)
Screen:restoreFromSavedBB()
fb:refresh(0)
end
)
return obj
end

@ -0,0 +1,486 @@
/*
KindlePDFViewer: CREngine abstraction for Lua
Copyright (C) 2012 Hans-Werner Hilse <hilse@web.de>
Qingping Hou <qingping.hou@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
extern "C" {
#include "blitbuffer.h"
#include "drawcontext.h"
#include "cre.h"
}
#include "crengine.h"
typedef struct CreDocument {
LVDocView *text_view;
ldomDocument *dom_doc;
} CreDocument;
static int openDocument(lua_State *L) {
const char *file_name = luaL_checkstring(L, 1);
const char *style_sheet = luaL_checkstring(L, 2);
int width = luaL_checkint(L, 3);
int height = luaL_checkint(L, 4);
lString8 css;
CreDocument *doc = (CreDocument*) lua_newuserdata(L, sizeof(CreDocument));
luaL_getmetatable(L, "credocument");
lua_setmetatable(L, -2);
doc->text_view = new LVDocView();
//doc->text_view->setBackgroundColor(0xFFFFFF);
//doc->text_view->setTextColor(0x000000);
if (LVLoadStylesheetFile(lString16(style_sheet), css)){
if (!css.empty()){
doc->text_view->setStyleSheet(css);
}
}
doc->text_view->setViewMode(DVM_SCROLL, -1);
doc->text_view->Resize(width, height);
doc->text_view->LoadDocument(file_name);
doc->dom_doc = doc->text_view->getDocument();
doc->text_view->Render();
return 1;
}
static int getGammaIndex(lua_State *L) {
lua_pushinteger(L, fontMan->GetGammaIndex());
return 1;
}
static int setGammaIndex(lua_State *L) {
int index = luaL_checkint(L, 1);
fontMan->SetGammaIndex(index);
return 0;
}
static int closeDocument(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
delete doc->text_view;
return 0;
}
static int getNumberOfPages(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
lua_pushinteger(L, doc->text_view->getPageCount());
return 1;
}
static int getCurrentPage(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
lua_pushinteger(L, doc->text_view->getCurPage());
return 1;
}
static int getPageFromXPointer(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
const char *xpointer_str = luaL_checkstring(L, 2);
int page = 0;
ldomXPointer xp = doc->dom_doc->createXPointer(lString16(xpointer_str));
page = doc->text_view->getBookmarkPage(xp);
lua_pushinteger(L, page);
return 1;
}
static int getCurrentPos(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
lua_pushinteger(L, doc->text_view->GetPos());
return 1;
}
//static int getPosFromXPointer(lua_State *L) {
//CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
//const char *xpointer_str = luaL_checkstring(L, 2);
//lvRect rc;
//int pos;
//ldomXPointer *xp = NULL;
//xp = doc->dom_doc->createXPointer(lString16(xpointer_str));
//getCursorDocRect(*xp, rc);
//pos =
//return 1;
//}
static int getCurrentPercent(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
lua_pushinteger(L, doc->text_view->getPosPercent());
return 1;
}
static int getXPointer(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
ldomXPointer xp = doc->text_view->getBookmark();
lua_pushstring(L, UnicodeToLocal(xp.toString()).c_str());
return 1;
}
static int getFullHeight(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
lua_pushinteger(L, doc->text_view->GetFullHeight());
return 1;
}
/*
* helper function for getTableOfContent()
*/
static int walkTableOfContent(lua_State *L, LVTocItem *toc, int *count) {
LVTocItem *toc_tmp = NULL;
int i = 0,
nr_child = toc->getChildCount();
for(i = 0; i < nr_child; i++) {
toc_tmp = toc->getChild(i);
lua_pushnumber(L, (*count)++);
/* set subtable, Toc entry */
lua_newtable(L);
lua_pushstring(L, "page");
lua_pushnumber(L, toc_tmp->getPercent());
lua_settable(L, -3);
lua_pushstring(L, "xpointer");
lua_pushstring(L, UnicodeToLocal(
toc_tmp->getXPointer().toString()).c_str()
);
lua_settable(L, -3);
lua_pushstring(L, "depth");
lua_pushnumber(L, toc_tmp->getLevel());
lua_settable(L, -3);
lua_pushstring(L, "title");
lua_pushstring(L, UnicodeToLocal(toc_tmp->getName()).c_str());
lua_settable(L, -3);
/* set Toc entry to Toc table */
lua_settable(L, -3);
if (toc_tmp->getChildCount() > 0) {
walkTableOfContent(L, toc_tmp, count);
}
}
return 0;
}
/*
* Return a table like this:
* {
* {
* page=12,
* xpointer = "/body/DocFragment[11].0",
* depth=1,
* title="chapter1"
* },
* {
* page=54,
* xpointer = "/body/DocFragment[13].0",
* depth=1,
* title="chapter2"
* },
* }
*
* Warnning: not like pdf or djvu support, page here refers to the
* percent of height within the document, not the real page number.
* We use page here just to keep consistent with other readers.
*
*/
static int getTableOfContent(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
LVTocItem * toc = doc->text_view->getToc();
int count = 0;
lua_newtable(L);
walkTableOfContent(L, toc, &count);
return 1;
}
/*
* Return a table like this:
* {
* "FreeMono",
* "FreeSans",
* "FreeSerif",
* }
*
*/
static int getFontFaces(lua_State *L) {
int i = 0;
lString16Collection face_list;
fontMan->getFaceList(face_list);
lua_newtable(L);
for (i = 0; i < face_list.length(); i++)
{
lua_pushnumber(L, i+1);
lua_pushstring(L, UnicodeToLocal(face_list[i]).c_str());
lua_settable(L, -3);
}
return 1;
}
static int setFontFace(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
const char *face = luaL_checkstring(L, 2);
doc->text_view->setDefaultFontFace(lString8(face));
return 0;
}
static int gotoPage(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
int pageno = luaL_checkint(L, 2);
doc->text_view->goToPage(pageno);
return 0;
}
static int gotoPercent(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
int percent = luaL_checkint(L, 2);
doc->text_view->SetPos(percent * doc->text_view->GetFullHeight() / 10000);
return 0;
}
static int gotoPos(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
int pos = luaL_checkint(L, 2);
doc->text_view->SetPos(pos);
return 0;
}
static int gotoXPointer(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
const char *xpointer_str = luaL_checkstring(L, 2);
ldomXPointer xp = doc->dom_doc->createXPointer(lString16(xpointer_str));
doc->text_view->goToBookmark(xp);
/* CREngine does not call checkPos() immediately after goToBookmark,
* so I have to manually update the pos in order to get a correct
* return from GetPos() call. */
doc->text_view->SetPos(xp.toPoint().y);
return 0;
}
/* zoom font by given delta and return zoomed font size */
static int zoomFont(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
int delta = luaL_checkint(L, 2);
doc->text_view->ZoomFont(delta);
lua_pushnumber(L, doc->text_view->getFontSize());
return 1;
}
static int setFontSize(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
int size = luaL_checkint(L, 2);
doc->text_view->setFontSize(size);
return 0;
}
static int setDefaultInterlineSpace(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
int space = luaL_checkint(L, 2);
doc->text_view->setDefaultInterlineSpace(space);
return 0;
}
static int setStyleSheet(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
const char* style_sheet_data = luaL_checkstring(L, 2);
doc->text_view->setStyleSheet(lString8(style_sheet_data));
return 0;
}
static int toggleFontBolder(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
doc->text_view->doCommand(DCMD_TOGGLE_BOLD);
return 0;
}
static int cursorRight(lua_State *L) {
//CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
//LVDocView *tv = doc->text_view;
//ldomXPointer p = tv->getCurrentPageMiddleParagraph();
//lString16 s = p.toString();
//printf("~~~~~~~~~~%s\n", UnicodeToLocal(s).c_str());
//tv->selectRange(*(tv->selectFirstPageLink()));
//ldomXRange *r = tv->selectNextPageLink(true);
//lString16 s = r->getRangeText();
//printf("------%s\n", UnicodeToLocal(s).c_str());
//tv->selectRange(*r);
//tv->updateSelections();
//LVPageWordSelector sel(doc->text_view);
//doc->text_view->doCommand(DCMD_SELECT_FIRST_SENTENCE);
//sel.moveBy(DIR_RIGHT, 2);
//sel.updateSelection();
//printf("---------------- %s\n", UnicodeToLocal(sel.getSelectedWord()->getText()).c_str());
return 0;
}
static int drawCurrentPage(lua_State *L) {
CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext");
BlitBuffer *bb = (BlitBuffer*) luaL_checkudata(L, 3, "blitbuffer");
int w = bb->w,
h = bb->h;
/* Set DrawBuf to 4bpp */
LVGrayDrawBuf drawBuf(w, h, 4);
doc->text_view->Resize(w, h);
doc->text_view->Render();
doc->text_view->Draw(drawBuf);
uint8_t *bbptr = (uint8_t*)bb->data;
uint8_t *pmptr = (uint8_t*)drawBuf.GetScanLine(0);
int i,x;
for (i = 0; i < h; i++) {
for (x = 0; x < (bb->w / 2); x++) {
/* When DrawBuf is set to 4bpp mode, CREngine still put every
* four bits in one byte, but left the last 4 bits zero*/
bbptr[x] = ~(pmptr[x*2] | (pmptr[x*2+1] >> 4));
}
if(bb->w & 1) {
bbptr[x] = 255 - (pmptr[x*2] & 0xF0);
}
bbptr += bb->pitch;
pmptr += w;
}
return 0;
}
static int registerFont(lua_State *L) {
const char *fontfile = luaL_checkstring(L, 1);
if ( !fontMan->RegisterFont(lString8(fontfile)) ) {
return luaL_error(L, "cannot register font <%s>", fontfile);
}
return 0;
}
static const struct luaL_Reg cre_func[] = {
{"openDocument", openDocument},
{"getFontFaces", getFontFaces},
{"getGammaIndex", getGammaIndex},
{"setGammaIndex", setGammaIndex},
{"registerFont", registerFont},
{NULL, NULL}
};
static const struct luaL_Reg credocument_meth[] = {
/*--- get methods ---*/
{"getPages", getNumberOfPages},
{"getCurrentPage", getCurrentPage},
{"getPageFromXPointer", getPageFromXPointer},
{"getCurrentPos", getCurrentPos},
{"getCurrentPercent", getCurrentPercent},
{"getXPointer", getXPointer},
{"getFullHeight", getFullHeight},
{"getToc", getTableOfContent},
/*--- set methods ---*/
{"setFontFace", setFontFace},
{"setFontSize", setFontSize},
{"setDefaultInterlineSpace", setDefaultInterlineSpace},
{"setStyleSheet", setStyleSheet},
/* --- control methods ---*/
{"gotoPage", gotoPage},
{"gotoPercent", gotoPercent},
{"gotoPos", gotoPos},
{"gotoXPointer", gotoXPointer},
{"zoomFont", zoomFont},
{"toggleFontBolder", toggleFontBolder},
//{"cursorLeft", cursorLeft},
//{"cursorRight", cursorRight},
{"drawCurrentPage", drawCurrentPage},
{"close", closeDocument},
{"__gc", closeDocument},
{NULL, NULL}
};
int luaopen_cre(lua_State *L) {
luaL_newmetatable(L, "credocument");
lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3);
luaL_register(L, NULL, credocument_meth);
lua_pop(L, 1);
luaL_register(L, "cre", cre_func);
/* initialize font manager for CREngine */
InitFontManager(lString8());
#ifdef DEBUG_CRENGINE
CRLog::setStdoutLogger();
CRLog::setLogLevel(CRLog::LL_DEBUG);
#endif
return 1;
}

28
cre.h

@ -0,0 +1,28 @@
/*
KindlePDFViewer: CREngine abstraction for Lua
Copyright (C) 2012 Hans-Werner Hilse <hilse@web.de>
Qingping Hou <qingping.hou@gmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _CRENGING_H
#define _CRENGING_H
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int luaopen_cre(lua_State *L);
#endif

@ -0,0 +1,375 @@
require "font"
require "unireader"
require "inputbox"
require "selectmenu"
CREReader = UniReader:new{
pos = nil,
percent = 0,
gamma_index = 15,
font_face = nil,
line_space_percent = 100,
}
function CREReader:init()
self:addAllCommands()
self:adjustCreReaderCommands()
-- we need to initialize the CRE font list
local fonts = Font:getFontList()
for _k, _v in ipairs(fonts) do
local ok, err = pcall(cre.registerFont, Font.fontdir..'/'.._v)
if not ok then
print(err)
end
end
end
-- open a CREngine supported file and its settings store
function CREReader:open(filename)
local ok
local file_type = string.lower(string.match(filename, ".+%.([^.]+)"))
-- these two format use the same css file
if file_type == "html" then
file_type = "htm"
end
local style_sheet = "./data/"..file_type..".css"
ok, self.doc = pcall(cre.openDocument, filename, style_sheet,
G_width, G_height)
if not ok then
return false, self.doc -- will contain error message
end
self.doc:setDefaultInterlineSpace(self.line_space_percent)
return true
end
----------------------------------------------------
-- setting related methods
----------------------------------------------------
function CREReader:loadSpecialSettings()
local font_face = self.settings:readSetting("font_face")
self.font_face = font_face or "FreeSerif"
self.doc:setFontFace(self.font_face)
local gamma_index = self.settings:readSetting("gamma_index")
self.gamma_index = gamma_index or self.gamma_index
cre.setGammaIndex(self.gamma_index)
local line_space_percent = self.settings:readSetting("line_space_percent")
self.line_space_percent = line_space_percent or self.line_space_percent
end
function CREReader:getLastPageOrPos()
local last_percent = self.settings:readSetting("last_percent")
if last_percent then
return math.floor((last_percent * self.doc:getFullHeight()) / 10000)
else
return 0
end
end
function CREReader:saveSpecialSettings()
self.settings:savesetting("font_face", self.font_face)
self.settings:savesetting("gamma_index", self.gamma_index)
self.settings:savesetting("line_space_percent", self.line_space_percent)
end
function CREReader:saveLastPageOrPos()
self.settings:savesetting("last_percent", self.percent)
end
----------------------------------------------------
-- render related methods
----------------------------------------------------
-- we don't need setzoom in CREReader
function CREReader:setzoom(page, preCache)
return
end
function CREReader:redrawCurrentPage()
self:goto(self.pos)
end
-- there is no zoom mode in CREReader
function CREReader:setGlobalZoomMode()
return
end
----------------------------------------------------
-- goto related methods
----------------------------------------------------
function CREReader:goto(pos, pos_type)
local prev_xpointer = self.doc:getXPointer()
local width, height = G_width, G_height
if pos_type == "xpointer" then
self.doc:gotoXPointer(pos)
pos = self.doc:getCurrentPos()
else -- pos_type is PERCENT * 100
pos = math.min(pos, self.doc:getFullHeight() - height)
pos = math.max(pos, 0)
self.doc:gotoPos(pos)
end
-- add to jump_stack, distinguish jump from normal page turn
-- NOTE:
-- even though we have called gotoPos() or gotoXPointer() previously,
-- self.pos hasn't been updated yet here, so we can still make use of it.
if self.pos and math.abs(self.pos - pos) > height then
self:addJump(prev_xpointer)
end
self.doc:drawCurrentPage(self.nulldc, fb.bb)
print("## self.show_overlap "..self.show_overlap)
if self.show_overlap < 0 then
fb.bb:dimRect(0,0, width, -self.show_overlap)
elseif self.show_overlap > 0 then
fb.bb:dimRect(0,height - self.show_overlap, width, self.show_overlap)
end
self.show_overlap = 0
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.pos = pos
print("------", self.pos)
self.pageno = self.doc:getCurrentPage()
self.percent = self.doc:getCurrentPercent()
end
function CREReader:gotoPercent(percent)
self:goto(percent * self.doc:getFullHeight() / 10000)
end
function CREReader:gotoTocEntry(entry)
self:goto(entry.xpointer, "xpointer")
end
function CREReader:nextView()
self.show_overlap = -self.pan_overlap_vertical
return self.pos + G_height - self.pan_overlap_vertical
end
function CREReader:prevView()
self.show_overlap = self.pan_overlap_vertical
return self.pos - G_height + self.pan_overlap_vertical
end
----------------------------------------------------
-- jump stack related methods
----------------------------------------------------
function CREReader:isSamePage(p1, p2)
return self.doc:getPageFromXPointer(p1) == self.doc:getPageFromXPointer(p2)
end
function CREReader:showJumpStack()
local menu_items = {}
print(dump(self.jump_stack))
for k,v in ipairs(self.jump_stack) do
table.insert(menu_items,
v.datetime.." -> page "..
(self.doc:getPageFromXPointer(v.page)).." "..v.notes)
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, "xpointer")
else
self:redrawCurrentPage()
end
end
----------------------------------------------------
-- TOC related methods
----------------------------------------------------
function CREReader:getTocTitleOfCurrentPage()
return self:getTocTitleByPage(self.percent)
end
----------------------------------------------------
-- menu related methods
----------------------------------------------------
-- used in CREReader:showMenu()
function CREReader:_drawReadingInfo()
local ypos = G_height - 50
local load_percent = self.percent/100
fb.bb:paintRect(0, ypos, G_width, 50, 0)
ypos = ypos + 15
local face = Font:getFace("rifont", 22)
local cur_section = self:getTocTitleOfCurrentPage()
if cur_section ~= "" then
cur_section = "Section: "..cur_section
end
renderUtf8Text(fb.bb, 10, ypos+6, face,
"Position: "..load_percent.."%".." "..cur_section, true)
ypos = ypos + 15
blitbuffer.progressBar(fb.bb, 10, ypos, G_width - 20, 15,
5, 4, load_percent/100, 8)
end
function CREReader:adjustCreReaderCommands()
-- delete commands
self.commands:delGroup("[joypad]")
self.commands:del(KEY_G, nil, "G")
self.commands:del(KEY_J, MOD_SHIFT, "J")
self.commands:del(KEY_K, MOD_SHIFT, "K")
self.commands:del(KEY_Z, nil, "Z")
self.commands:del(KEY_Z, MOD_SHIFT, "Z")
self.commands:del(KEY_Z, MOD_ALT, "Z")
self.commands:del(KEY_A, nil, "A")
self.commands:del(KEY_A, MOD_SHIFT, "A")
self.commands:del(KEY_A, MOD_ALT, "A")
self.commands:del(KEY_S, nil, "S")
self.commands:del(KEY_S, MOD_SHIFT, "S")
self.commands:del(KEY_S, MOD_ALT, "S")
self.commands:del(KEY_D, nil, "D")
self.commands:del(KEY_D, MOD_SHIFT, "D")
self.commands:del(KEY_D, MOD_ALT, "D")
self.commands:del(KEY_F, MOD_SHIFT, "F")
self.commands:del(KEY_F, MOD_ALT, "F")
self.commands:del(KEY_N, nil, "N") -- highlight
self.commands:del(KEY_N, MOD_SHIFT, "N") -- show highlights
-- overwrite commands
self.commands:add(KEY_PGFWD, MOD_SHIFT, ">",
"increase font size",
function(cr)
cr.doc:zoomFont(1)
cr:redrawCurrentPage()
end
)
self.commands:add(KEY_PGBCK, MOD_SHIFT, "<",
"decrease font size",
function(cr)
cr.doc:zoomFont(-1)
cr:redrawCurrentPage()
end
)
self.commands:add(KEY_PGFWD, MOD_ALT, ">",
"increase line spacing",
function(cr)
self.line_space_percent = self.line_space_percent + 10
if self.line_space_percent > 200 then
self.line_space_percent = 200
end
print("line spacing set to", self.line_space_percent)
cr.doc:setDefaultInterlineSpace(self.line_space_percent)
cr:redrawCurrentPage()
end
)
self.commands:add(KEY_PGBCK, MOD_ALT, "<",
"decrease line spacing",
function(cr)
self.line_space_percent = self.line_space_percent - 10
if self.line_space_percent < 100 then
self.line_space_percent = 100
end
print("line spacing set to", self.line_space_percent)
cr.doc:setDefaultInterlineSpace(self.line_space_percent)
cr:redrawCurrentPage()
end
)
local numeric_keydefs = {}
for i=1,10 do
numeric_keydefs[i]=Keydef:new(KEY_1+i-1, nil, tostring(i%10))
end
self.commands:addGroup("[1..0]", numeric_keydefs,
"jump to <key>*10% of document",
function(cr, keydef)
print('jump to position: '..
math.floor(cr.doc:getFullHeight()*(keydef.keycode-KEY_1)/9)..
'/'..cr.doc:getFullHeight())
cr:goto(math.floor(cr.doc:getFullHeight()*(keydef.keycode-KEY_1)/9))
end
)
self.commands:add(KEY_F, nil, "F",
"invoke font menu",
function(cr)
local face_list = cre.getFontFaces()
local fonts_menu = SelectMenu:new{
menu_title = "Fonts Menu",
item_array = face_list,
}
local item_no = fonts_menu:choose(0, G_height)
print(face_list[item_no])
if item_no then
cr.doc:setFontFace(face_list[item_no])
self.font_face = face_list[item_no]
end
cr:redrawCurrentPage()
end
)
self.commands:add(KEY_F, MOD_ALT, "F",
"Toggle font bolder attribute",
function(cr)
cr.doc:toggleFontBolder()
cr:redrawCurrentPage()
end
)
self.commands:add(KEY_B, MOD_SHIFT, "B",
"add jump",
function(cr)
cr:addJump(self.doc:getXPointer())
end
)
self.commands:add(KEY_BACK,nil,"back",
"back to last jump",
function(cr)
if #cr.jump_stack ~= 0 then
cr:goto(cr.jump_stack[1].page, "xpointer")
end
end
)
self.commands:add(KEY_VPLUS, nil, "vol+",
"increase gamma",
function(cr)
cre.setGammaIndex(self.gamma_index + 1)
self.gamma_index = cre.getGammaIndex()
cr:redrawCurrentPage()
end
)
self.commands:add(KEY_VMINUS, nil, "vol-",
"decrease gamma",
function(cr)
cre.setGammaIndex(self.gamma_index - 1)
self.gamma_index = cre.getGammaIndex()
cr:redrawCurrentPage()
end
)
self.commands:add(KEY_FW_UP, nil, "joypad up",
"pan "..self.shift_y.." pixels upwards",
function(cr)
cr:goto(cr.pos - cr.shift_y)
end
)
self.commands:add(KEY_FW_DOWN, nil, "joypad down",
"pan "..self.shift_y.." pixels downwards",
function(cr)
cr:goto(cr.pos + cr.shift_y)
end
)
end

@ -0,0 +1,43 @@
require "widget"
require "font"
InfoMessage = {
face = Font:getFace("infofont", 25)
}
function InfoMessage:show(text)
local dialog = CenterContainer:new({
dimen = { w = G_width, h = G_height },
FrameContainer:new({
margin = 2,
background = 0,
HorizontalGroup:new({
align = "center",
ImageWidget:new({
file = "resources/info-i.png"
}),
Widget:new({
dimen = { w = 10, h = 0 }
}),
TextWidget:new({
text = text,
face = Font:getFace("cfont", 30)
})
})
})
})
dialog:paintTo(fb.bb, 0, 0)
dialog:free()
end
function showInfoMsgWithDelay(text, msec, refresh_mode)
if not refresh_mode then refresh_mode = 0 end
Screen:saveCurrentBB()
InfoMessage:show(text)
fb:refresh(refresh_mode)
util.usleep(msec*1000)
Screen:restoreFromSavedBB()
fb:refresh(refresh_mode)
end

149
djvu.c

@ -71,7 +71,7 @@ static int handle(lua_State *L, ddjvu_context_t *ctx, int wait)
static int openDocument(lua_State *L) {
const char *filename = luaL_checkstring(L, 1);
/*const char *password = luaL_checkstring(L, 2);*/
int cache_size = luaL_optint(L, 2, 10 << 20);
DjvuDocument *doc = (DjvuDocument*) lua_newuserdata(L, sizeof(DjvuDocument));
luaL_getmetatable(L, "djvudocument");
@ -82,6 +82,9 @@ static int openDocument(lua_State *L) {
return luaL_error(L, "cannot create context.");
}
printf("## cache_size = %d\n", cache_size);
ddjvu_cache_set_size(doc->context, (unsigned long)cache_size);
doc->doc_ref = ddjvu_document_create_by_filename_utf8(doc->context, filename, TRUE);
while (! ddjvu_document_decoding_done(doc->doc_ref))
handle(L, doc->context, True);
@ -206,7 +209,7 @@ static int openPage(lua_State *L) {
static int getPageSize(lua_State *L) {
DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage");
DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext");
lua_pushnumber(L, dc->zoom * page->info.width);
lua_pushnumber(L, dc->zoom * page->info.height);
@ -225,6 +228,128 @@ static int getUsedBBox(lua_State *L) {
return 4;
}
/*
* Return a table like following:
* {
* -- a line entry
* 1 = {
* 1 = {word="This", x0=377, y0=4857, x1=2427, y1=5089},
* 2 = {word="is", x0=377, y0=4857, x1=2427, y1=5089},
* 3 = {word="Word", x0=377, y0=4857, x1=2427, y1=5089},
* 4 = {word="List", x0=377, y0=4857, x1=2427, y1=5089},
* x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089,
* },
*
* -- an other line entry
* 2 = {
* 1 = {word="This", x0=377, y0=4857, x1=2427, y1=5089},
* 2 = {word="is", x0=377, y0=4857, x1=2427, y1=5089},
* x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089,
* },
* }
*/
static int getPageText(lua_State *L) {
DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument");
int pageno = luaL_checkint(L, 2);
miniexp_t sexp, se_line, se_word;
int i = 1, j = 1, counter_l = 1, counter_w=1,
nr_line = 0, nr_word = 0;
const char *word = NULL;
while ((sexp = ddjvu_document_get_pagetext(doc->doc_ref, pageno-1, "word"))
== miniexp_dummy) {
handle(L, doc->context, True);
}
/* throuw page info and obtain lines info, after this, sexp's entries
* are lines. */
sexp = miniexp_cdr(sexp);
/* get number of lines in a page */
nr_line = miniexp_length(sexp);
/* table that contains all the lines */
lua_newtable(L);
counter_l = 1;
for(i = 1; i <= nr_line; i++) {
/* retrive one line entry */
se_line = miniexp_nth(i, sexp);
nr_word = miniexp_length(se_line);
if(nr_word == 0) {
continue;
}
/* subtable that contains words in a line */
lua_pushnumber(L, counter_l);
lua_newtable(L);
counter_l++;
/* set line position */
lua_pushstring(L, "x0");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(1, se_line)));
lua_settable(L, -3);
lua_pushstring(L, "y0");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(2, se_line)));
lua_settable(L, -3);
lua_pushstring(L, "x1");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(3, se_line)));
lua_settable(L, -3);
lua_pushstring(L, "y1");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(4, se_line)));
lua_settable(L, -3);
/* now loop through each word in the line */
counter_w = 1;
for(j = 1; j <= nr_word; j++) {
/* retrive one word entry */
se_word = miniexp_nth(j, se_line);
/* check to see whether the entry is empty */
word = miniexp_to_str(miniexp_nth(5, se_word));
if (!word) {
continue;
}
/* create table that contains info for a word */
lua_pushnumber(L, counter_w);
lua_newtable(L);
counter_w++;
/* set word info */
lua_pushstring(L, "x0");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(1, se_word)));
lua_settable(L, -3);
lua_pushstring(L, "y0");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(2, se_word)));
lua_settable(L, -3);
lua_pushstring(L, "x1");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(3, se_word)));
lua_settable(L, -3);
lua_pushstring(L, "y1");
lua_pushnumber(L, miniexp_to_int(miniexp_nth(4, se_word)));
lua_settable(L, -3);
lua_pushstring(L, "word");
lua_pushstring(L, word);
lua_settable(L, -3);
/* set word entry to line subtable */
lua_settable(L, -3);
} /* end of for (j) */
/* set line entry to page text table */
lua_settable(L, -3);
} /* end of for (i) */
return 1;
}
static int closePage(lua_State *L) {
DjvuPage *page = (DjvuPage*) luaL_checkudata(L, 1, "djvupage");
if(page->page_ref != NULL) {
@ -325,6 +450,21 @@ static int drawPage(lua_State *L) {
return 0;
}
static int getCacheSize(lua_State *L) {
DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument");
unsigned long size = ddjvu_cache_get_size(doc->context);
printf("## ddjvu_cache_get_size = %d\n", size);
lua_pushnumber(L, size);
return 1;
}
static int cleanCache(lua_State *L) {
DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument");
printf("## ddjvu_cache_clear\n");
ddjvu_cache_clear(doc->context);
return 0;
}
static const struct luaL_Reg djvu_func[] = {
{"openDocument", openDocument},
{NULL, NULL}
@ -333,8 +473,11 @@ static const struct luaL_Reg djvu_func[] = {
static const struct luaL_Reg djvudocument_meth[] = {
{"openPage", openPage},
{"getPages", getNumberOfPages},
{"getTOC", getTableOfContent},
{"getToc", getTableOfContent},
{"getPageText", getPageText},
{"close", closeDocument},
{"getCacheSize", getCacheSize},
{"cleanCache", cleanCache},
{"__gc", closeDocument},
{NULL, NULL}
};

@ -6,9 +6,81 @@ DJVUReader = UniReader:new{}
-- DJVU does not support password yet
function DJVUReader:open(filename)
local ok
ok, self.doc = pcall(djvu.openDocument, filename)
ok, self.doc = pcall(djvu.openDocument, filename, self.cache_document_size)
if not ok then
return ok, self.doc -- this will be the error message instead
end
return ok
end
function DJVUReader:init()
self:addAllCommands()
self:adjustDjvuReaderCommand()
end
function DJVUReader:adjustDjvuReaderCommand()
self.commands:del(KEY_J, MOD_SHIFT, "J")
self.commands:del(KEY_K, MOD_SHIFT, "K")
end
----------------------------------------------------
-- highlight support
----------------------------------------------------
function DJVUReader:getText(pageno)
return self.doc:getPageText(pageno)
end
----------------------------------------------------
-- In djvulibre library, some coordinates starts from
-- lower left conner, i.e. y is upside down in kpv's
-- coordinate. So y0 should be taken with special care.
----------------------------------------------------
function DJVUReader:zoomedRectCoordTransform(x0, y0, x1, y1)
local x,y = self:screenOffset()
return
x0 * self.globalzoom + x,
self.cur_full_height - (y1 * self.globalzoom) + y,
(x1 - x0) * self.globalzoom,
(y1 - y0) * self.globalzoom
end
-- y axel in djvulibre starts from bottom
function DJVUReader:_isEntireWordInScreenHeightRange(w)
return (w ~= nil) and
(self.cur_full_height - (w.y1 * self.globalzoom) >=
-self.offset_y) and
(self.cur_full_height - (w.y0 * self.globalzoom) <=
-self.offset_y + G_height)
end
-- y axel in djvulibre starts from bottom
function DJVUReader:_isEntireLineInScreenHeightRange(l)
return (l ~= nil) and
(self.cur_full_height - (l.y1 * self.globalzoom) >=
-self.offset_y) and
(self.cur_full_height - (l.y0 * self.globalzoom) <=
-self.offset_y + G_height)
end
-- y axel in djvulibre starts from bottom
function DJVUReader:_isWordInScreenRange(w)
if not w then
return false
end
is_entire_word_out_of_screen_height =
(self.cur_full_height - (w.y0 * self.globalzoom) <=
-self.offset_y)
or (self.cur_full_height - (w.y1 * self.globalzoom) >=
-self.offset_y + G_height)
is_entire_word_out_of_screen_width =
(w.x0 * self.globalzoom >= -self.offset_x + G_width
or w.x1 * self.globalzoom <= -self.offset_x)
return (not is_entire_word_out_of_screen_height) and
(not is_entire_word_out_of_screen_width)
end

@ -150,6 +150,16 @@ static int einkUpdate(lua_State *L) {
ioctl(fb->fd, FBIO_EINK_UPDATE_DISPLAY_AREA, &myarea);
#else
// for now, we only do fullscreen blits in emulation mode
if (fxtype == 0) {
// simmulate a full screen update in eink screen
if(SDL_MUSTLOCK(fb->screen) && (SDL_LockSurface(fb->screen) < 0)) {
return luaL_error(L, "can't lock surface.");
}
SDL_FillRect(fb->screen, NULL, 0x000000);
if(SDL_MUSTLOCK(fb->screen)) SDL_UnlockSurface(fb->screen);
SDL_Flip(fb->screen);
}
if(SDL_MUSTLOCK(fb->screen) && (SDL_LockSurface(fb->screen) < 0)) {
return luaL_error(L, "can't lock surface.");
}

@ -54,7 +54,11 @@ function FileChooser:readDir()
table.insert(self.dirs, f)
else
local file_type = string.lower(string.match(f, ".+%.([^.]+)") or "")
if file_type == "djvu" or file_type == "pdf" or file_type == "xps" or file_type == "cbz" then
if file_type == "djvu"
or file_type == "pdf" or file_type == "xps" or file_type == "cbz"
or file_type == "epub" or file_type == "txt" or file_type == "rtf"
or file_type == "htm" or file_type == "html"
or file_type == "fb2" or file_type == "chm" then
table.insert(self.files, f)
end
end
@ -118,8 +122,8 @@ function FileChooser:choose(ypos, height)
end
while true do
local cface, cfhash= Font:getFaceAndHash(25)
local fface, ffhash = Font:getFaceAndHash(16, Font.ffont)
local cface = Font:getFace("cfont", 25)
local fface = Font:getFace("ffont", 16)
if pagedirty then
fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0)
@ -128,17 +132,17 @@ function FileChooser:choose(ypos, height)
local i = (self.page - 1) * perpage + c
if i <= #self.dirs then
-- resembles display in midnight commander: adds "/" prefix for directories
renderUtf8Text(fb.bb, 39, ypos + self.spacing*c, cface, cfhash, "/", true)
renderUtf8Text(fb.bb, 50, ypos + self.spacing*c, cface, cfhash, self.dirs[i], true)
renderUtf8Text(fb.bb, 39, ypos + self.spacing*c, cface, "/", true)
renderUtf8Text(fb.bb, 50, ypos + self.spacing*c, cface, self.dirs[i], true)
elseif i <= self.items then
renderUtf8Text(fb.bb, 50, ypos + self.spacing*c, cface, cfhash, self.files[i-#self.dirs], true)
renderUtf8Text(fb.bb, 50, ypos + self.spacing*c, cface, self.files[i-#self.dirs], true)
end
end
renderUtf8Text(fb.bb, 5, ypos + self.spacing * perpage + 42, fface, ffhash,
renderUtf8Text(fb.bb, 5, ypos + self.spacing * perpage + 42, fface,
"Page "..self.page.." of "..(math.floor(self.items / perpage)+1), true)
local msg = self.exception_message and self.exception_message:match("[^%:]+:%d+: (.*)") or "Path: "..self.path
self.exception_message = nil
renderUtf8Text(fb.bb, 5, ypos + self.spacing * (perpage+1) + 27, fface, ffhash, msg, true)
renderUtf8Text(fb.bb, 5, ypos + self.spacing * (perpage+1) + 27, fface, msg, true)
markerdirty = true
end
if markerdirty then
@ -160,7 +164,7 @@ function FileChooser:choose(ypos, height)
pagedirty = false
end
local ev = input.waitForEvent()
local ev = input.saveWaitForEvent()
--print("key code:"..ev.code)
ev.code = adjustKeyEvents(ev)
if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then
@ -169,13 +173,13 @@ function FileChooser:choose(ypos, height)
elseif ev.code == KEY_FW_DOWN then
nextItem()
elseif ev.code == KEY_F then -- invoke fontchooser menu
fonts_menu = SelectMenu:new{
local fonts_menu = SelectMenu:new{
menu_title = "Fonts Menu",
item_array = Font.fonts,
item_array = Font:getFontList(),
}
local re = fonts_menu:choose(0, height)
local re, font = fonts_menu:choose(0, height)
if re then
Font.cfont = Font.fonts[re]
Font.fontmap["cfont"] = font
Font:update()
end
pagedirty = true
@ -193,11 +197,11 @@ function FileChooser:choose(ypos, height)
--]]
return nil, function()
FileSearcher:init( self.path )
FileSearcher:choose(ypos, height, keywords)
FileSearcher:choose(keywords)
end
end
pagedirty = true
elseif ev.code == KEY_PGFWD then
elseif ev.code == KEY_PGFWD or ev.code == KEY_LPGFWD then
if self.page < (self.items / perpage) then
if self.current + self.page*perpage > self.items then
self.current = self.items - self.page*perpage
@ -208,7 +212,7 @@ function FileChooser:choose(ypos, height)
self.current = self.items - (self.page-1)*perpage
markerdirty = true
end
elseif ev.code == KEY_PGBCK then
elseif ev.code == KEY_PGBCK or ev.code == KEY_LPGBCK then
if self.page > 1 then
self.page = self.page - 1
pagedirty = true

@ -30,10 +30,16 @@ function FileSearcher:readDir()
for __, d in pairs(self.dirs) do
-- handle files in d
for f in lfs.dir(d) do
local file_type = string.lower(string.match(f, ".+%.([^.]+)") or "")
if lfs.attributes(d.."/"..f, "mode") == "directory"
and f ~= "." and f~= ".." and not string.match(f, "^%.[^.]") then
table.insert(new_dirs, d.."/"..f)
elseif string.match(f, ".+%.[pP][dD][fF]$") or string.match(f, ".+%.[dD][jJ][vV][uU]$") then
elseif file_type == "djvu" or file_type == "pdf"
or file_type == "xps" or file_type == "cbz"
or file_type == "epub" or file_type == "txt"
or file_type == "rtf" or file_type == "htm"
or file_type == "html"
or file_type == "fb2" or file_type == "chm" then
file_entry = {dir=d, name=f,}
table.insert(self.files, file_entry)
--print("file:"..d.."/"..f)
@ -68,6 +74,7 @@ function FileSearcher:setSearchResult(keywords)
end
end
end
self.keywords = keywords
self.items = #self.result
self.page = 1
self.current = 1
@ -79,42 +86,138 @@ function FileSearcher:init(search_path)
else
self:setPath("/mnt/us/documents")
end
self:addAllCommands()
end
function FileSearcher:prevItem()
if self.current == 1 then
if self.page > 1 then
self.current = self.perpage
self.page = self.page - 1
self.pagedirty = true
end
else
self.current = self.current - 1
self.markerdirty = true
end
end
function FileSearcher:nextItem()
if self.current == self.perpage then
if self.page < (self.items / self.perpage) then
self.current = 1
self.page = self.page + 1
self.pagedirty = true
end
else
if self.page ~= math.floor(self.items / self.perpage) + 1
or self.current + (self.page-1)*self.perpage < self.items then
self.current = self.current + 1
self.markerdirty = true
end
end
end
function FileSearcher:choose(ypos, height, keywords)
local perpage = math.floor(height / self.spacing) - 2
local pagedirty = true
local markerdirty = false
function FileSearcher:addAllCommands()
self.commands = Commands:new{}
local prevItem = function ()
if self.current == 1 then
self.commands:add(KEY_FW_UP, nil, "joypad up",
"goto previous item",
function(self)
self:prevItem()
end
)
self.commands:add(KEY_FW_DOWN, nil, "joypad down",
"goto next item",
function(self)
self:nextItem()
end
)
self.commands:add(KEY_PGFWD, nil, ">",
"next page",
function(self)
if self.page < (self.items / self.perpage) then
if self.current + self.page*self.perpage > self.items then
self.current = self.items - self.page*self.perpage
end
self.page = self.page + 1
self.pagedirty = true
else
self.current = self.items - (self.page-1)*self.perpage
self.markerdirty = true
end
end
)
self.commands:add(KEY_PGBCK, nil, "<",
"previous page",
function(self)
if self.page > 1 then
self.current = perpage
self.page = self.page - 1
pagedirty = true
self.pagedirty = true
else
self.current = 1
self.markerdirty = true
end
else
self.current = self.current - 1
markerdirty = true
end
end
local nextItem = function ()
if self.current == perpage then
if self.page < (self.items / perpage) then
self.current = 1
self.page = self.page + 1
pagedirty = true
)
self.commands:add(KEY_S, nil, "S",
"invoke search inputbox",
function(self)
old_keywords = self.keywords
self.keywords = InputBox:input(G_height - 100, 100,
"Search:", old_keywords)
if self.keywords then
self:setSearchResult(self.keywords)
else
self.keywords = old_keywords
end
else
if self.page ~= math.floor(self.items / perpage) + 1
or self.current + (self.page-1)*perpage < self.items then
self.current = self.current + 1
markerdirty = true
self.pagedirty = true
end
)
self.commands:add(KEY_F, nil, "F",
"font menu",
function(self)
local fonts_menu = SelectMenu:new{
menu_title = "Fonts Menu",
item_array = Font:getFontList(),
}
local re, font = fonts_menu:choose(0, G_height)
if re then
Font.fontmap["cfont"] = font
Font:update()
end
self.pagedirty = true
end
end
)
self.commands:add({KEY_ENTER, KEY_FW_PRESS}, nil, "",
"select item",
function(self)
file_entry = self.result[self.perpage*(self.page-1)+self.current]
file_full_path = file_entry.dir .. "/" .. file_entry.name
openFile(file_full_path)
--reset height and item index if screen has been rotated
local item_no = self.perpage * (self.page - 1) + self.current
self.perpage = math.floor(G_height / self.spacing) - 2
self.current = item_no % self.perpage
self.page = math.floor(item_no / self.perpage) + 1
self.pagedirty = true
end
)
self.commands:add({KEY_BACK, KEY_HOME}, nil, "",
"back to file browser",
function(self)
return "break"
end
)
end
function FileSearcher:choose(keywords)
self.perpage = math.floor(G_height / self.spacing) - 2
self.pagedirty = true
self.markerdirty = false
-- if given keywords, set new result according to keywords.
-- Otherwise, display the previous search result.
@ -123,136 +226,92 @@ function FileSearcher:choose(ypos, height, keywords)
end
while true do
local cface, cfhash = Font:getFaceAndHash(22)
local tface, tfhash = Font:getFaceAndHash(25, Font.tfont)
local fface, ffhash = Font:getFaceAndHash(16, Font.ffont)
local cface = Font:getFace("cfont", 22)
local tface = Font:getFace("tfont", 25)
local fface = Font:getFace("ffont", 16)
if pagedirty then
markerdirty = true
fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0)
if self.pagedirty then
self.markerdirty = true
fb.bb:paintRect(0, 0, G_width, G_height, 0)
-- draw menu title
renderUtf8Text(fb.bb, 30, ypos + self.title_H, tface, tfhash,
"Search Result for: "..keywords, true)
renderUtf8Text(fb.bb, 30, 0 + self.title_H, tface,
"Search Result for: "..self.keywords, true)
-- draw results
local c
if self.items == 0 then -- nothing found
y = ypos + self.title_H + self.spacing * 2
renderUtf8Text(fb.bb, 20, y, cface, cfhash,
y = self.title_H + self.spacing * 2
renderUtf8Text(fb.bb, 20, y, cface,
"Sorry, no match found.", true)
renderUtf8Text(fb.bb, 20, y + self.spacing, cface, cfhash,
renderUtf8Text(fb.bb, 20, y + self.spacing, cface,
"Please try a different keyword.", true)
markerdirty = false
self.markerdirty = false
else -- found something, draw it
for c = 1, perpage do
local i = (self.page - 1) * perpage + c
for c = 1, self.perpage do
local i = (self.page - 1) * self.perpage + c
if i <= self.items then
y = ypos + self.title_H + (self.spacing * c)
renderUtf8Text(fb.bb, 50, y, cface, cfhash,
y = self.title_H + (self.spacing * c)
renderUtf8Text(fb.bb, 50, y, cface,
self.result[i].name, true)
end
end
end
-- draw footer
y = ypos + self.title_H + (self.spacing * perpage) + self.foot_H
x = (fb.bb:getWidth() / 2) - 50
all_page = (math.floor(self.items / perpage)+1)
renderUtf8Text(fb.bb, x, y, fface, ffhash,
y = self.title_H + (self.spacing * self.perpage) + self.foot_H
x = (G_width / 2) - 50
all_page = (math.floor(self.items / self.perpage)+1)
renderUtf8Text(fb.bb, x, y, fface,
"Page "..self.page.." of "..all_page, true)
end
if markerdirty then
if not pagedirty then
if self.markerdirty then
if not self.pagedirty then
if self.oldcurrent > 0 then
y = ypos + self.title_H + (self.spacing * self.oldcurrent) + 10
fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 0)
fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3)
y = self.title_H + (self.spacing * self.oldcurrent) + 10
fb.bb:paintRect(30, y, G_width - 60, 3, 0)
fb:refresh(1, 30, y, G_width - 60, 3)
end
end
-- draw new marker line
y = ypos + self.title_H + (self.spacing * self.current) + 10
fb.bb:paintRect(30, y, fb.bb:getWidth() - 60, 3, 15)
if not pagedirty then
fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3)
y = self.title_H + (self.spacing * self.current) + 10
fb.bb:paintRect(30, y, G_width - 60, 3, 15)
if not self.pagedirty then
fb:refresh(1, 30, y, G_width - 60, 3)
end
self.oldcurrent = self.current
markerdirty = false
self.markerdirty = false
end
if pagedirty then
fb:refresh(0, 0, ypos, fb.bb:getWidth(), height)
pagedirty = false
if self.pagedirty then
fb:refresh(0)
self.pagedirty = false
end
local ev = input.waitForEvent()
local ev = input.saveWaitForEvent()
ev.code = adjustKeyEvents(ev)
if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then
if ev.code == KEY_FW_UP then
prevItem()
elseif ev.code == KEY_FW_DOWN then
nextItem()
elseif ev.code == KEY_PGFWD then
if self.page < (self.items / perpage) then
if self.current + self.page*perpage > self.items then
self.current = self.items - self.page*perpage
end
self.page = self.page + 1
pagedirty = true
else
self.current = self.items - (self.page-1)*perpage
markerdirty = true
end
elseif ev.code == KEY_PGBCK then
if self.page > 1 then
self.page = self.page - 1
pagedirty = true
else
self.current = 1
markerdirty = true
end
elseif ev.code == KEY_S then
old_keywords = keywords
keywords = InputBox:input(height-100, 100, "Search:", old_keywords)
if keywords then
self:setSearchResult(keywords)
else
keywords = old_keywords
end
pagedirty = true
elseif ev.code == KEY_F then -- invoke fontchooser menu
fonts_menu = SelectMenu:new{
menu_title = "Fonts Menu",
item_array = Font.fonts,
}
local re = fonts_menu:choose(0, height)
if re then
Font.cfont = Font.fonts[re]
Font:update()
end
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_full_path = file_entry.dir .. "/" .. file_entry.name
keydef = Keydef:new(ev.code, getKeyModifier())
print("key pressed: "..tostring(keydef))
-- rotation mode might be changed while reading, so
-- record height_percent here
local height_percent = height/fb.bb:getHeight()
openFile(file_full_path)
command = self.commands:getByKeydef(keydef)
if command ~= nil then
print("command to execute: "..tostring(command))
ret_code = command.func(self, keydef)
else
print("command not found: "..tostring(command))
end
--reset height and item index if screen has been rotated
local old_perpage = perpage
height = math.floor(fb.bb:getHeight()*height_percent)
perpage = math.floor(height / self.spacing) - 2
self.current = (old_perpage * (self.page - 1) +
self.current) % perpage
self.page = math.floor(self.items / perpage) + 1
if ret_code == "break" then
break
end
pagedirty = true
elseif ev.code == KEY_BACK or ev.code == KEY_HOME then
return nil
if self.selected_item ~= nil then
print("# selected "..self.selected_item)
return self.selected_item
end
end
end
end -- if
end -- while true
return nil
end

@ -1,23 +1,43 @@
Font = {
-- default font for menu contents
cfont = "sans",
-- default font for title
tfont = "Helvetica-BoldOblique",
-- default font for footer
ffont = "sans",
-- built in fonts
fonts = {"sans", "cjk", "mono",
"Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique",
"Helvetica", "Helvetica-Oblique", "Helvetica-BoldOblique",
"Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic",},
fontmap = {
-- default font for menu contents
cfont = "droid/DroidSans.ttf",
-- default font for title
tfont = "NimbusSanL-BoldItal.cff",
-- default font for footer
ffont = "droid/DroidSans.ttf",
-- default font for reading position info
rifont = "droid/DroidSans.ttf",
-- default font for pagination display
pgfont = "droid/DroidSans.ttf",
-- selectmenu: font for item shortcut
scfont = "droid/DroidSansMono.ttf",
-- help page: font for displaying keys
hpkfont = "droid/DroidSansMono.ttf",
-- font for displaying help messages
hfont = "droid/DroidSans.ttf",
-- font for displaying input content
-- we have to use mono here for better distance controlling
infont = "droid/DroidSansMono.ttf",
-- font for info messages
infofont = "droid/DroidSans.ttf",
},
fontdir = os.getenv("FONTDIR") or "./fonts",
-- face table
faces = {},
}
function Font:getFaceAndHash(size, font)
function Font:getFace(font, size)
if not font then
-- default to content font
font = self.cfont
@ -26,21 +46,45 @@ function Font:getFaceAndHash(size, font)
local face = self.faces[font..size]
-- build face if not found
if not face then
for _k,_v in ipairs(self.fonts) do
if font == _v then
face = freetype.newBuiltinFace(font, size)
self.faces[font..size] = face
end
local realname = self.fontmap[font]
if not realname then
realname = font
end
if not face then
print("#! Font "..font.." not supported!!")
realname = self.fontdir.."/"..realname
ok, face = pcall(freetype.newFace, realname, size)
if not ok then
print("#! Font "..font.." ("..realname..") not supported: "..face)
return nil
end
self.faces[font..size] = face
end
return face, font..size
return { size = size, ftface = face, hash = font..size }
end
function Font:_readList(target, dir, effective_dir)
for f in lfs.dir(dir) do
if lfs.attributes(dir.."/"..f, "mode") == "directory" and f ~= "." and f ~= ".." then
self:_readList(target, dir.."/"..f, effective_dir..f.."/")
else
local file_type = string.lower(string.match(f, ".+%.([^.]+)") or "")
if file_type == "ttf" or file_type == "cff" or file_type == "otf" then
table.insert(target, effective_dir..f)
end
end
end
end
function Font:getFontList()
fontlist = {}
self:_readList(fontlist, self.fontdir, "")
table.sort(fontlist)
return fontlist
end
function Font:update()
for _k, _v in ipairs(self.faces) do
_v:done()
end
self.faces = {}
clearGlyphCache()
end

44
ft.c

@ -55,49 +55,6 @@ static int newFace(lua_State *L) {
return 1;
}
static int newBuiltinFace(lua_State *L) {
const char *fontname = luaL_checkstring(L, 1);
int pxsize = luaL_optint(L, 2, 16*64);
char *fontdata = NULL;
unsigned int size;
/* we use compiled-in font data from mupdf build */
if(!strcmp("mono", fontname)) {
fontdata = pdf_lookup_substitute_font(1, 0, 0, 0, &size);
} else if(!strcmp("sans", fontname)) {
fontdata = pdf_lookup_substitute_font(0, 0, 0, 0, &size);
} else if(!strcmp("cjk", fontname)) {
fontdata = pdf_lookup_substitute_cjk_font(0, 0, &size);
} else {
fontdata = pdf_lookup_builtin_font(fontname, &size);
}
if(fontdata == NULL) {
return luaL_error(L, "no such built-in font");
}
FT_Face *face = (FT_Face*) lua_newuserdata(L, sizeof(FT_Face));
luaL_getmetatable(L, "ft_face");
lua_setmetatable(L, -2);
FT_Error error = FT_New_Memory_Face(freetypelib, (FT_Byte*)fontdata, size, 0, face);
if(error) {
return luaL_error(L, "freetype error");
}
error = FT_Set_Pixel_Sizes(*face, 0, pxsize);
if(error) {
error = FT_Done_Face(*face);
return luaL_error(L, "freetype error");
}
if((*face)->charmap == NULL) {
//TODO
//fprintf(stderr, "no unicode charmap found, to be implemented.\n");
}
return 1;
}
static int renderGlyph(lua_State *L) {
FT_Face *face = (FT_Face*) luaL_checkudata(L, 1, "ft_face");
int ch = luaL_checkint(L, 2);
@ -215,7 +172,6 @@ static const struct luaL_Reg ft_face_meth[] = {
static const struct luaL_Reg ft_func[] = {
{"newFace", newFace},
{"newBuiltinFace", newBuiltinFace},
{NULL, NULL}
};

@ -6,6 +6,7 @@ blitbuffer.paintBorder = function (bb, x, y, w, h, bw, c)
bb:paintRect(x+w-bw, y+bw, bw, h - 2*bw, c)
end
--[[
Draw a progress bar according to following args:
@ -27,3 +28,108 @@ blitbuffer.progressBar = function (bb, x, y, w, h,
fb.bb:paintRect(x+load_m_w, y+load_m_h,
(w-2*load_m_w)*load_percent, (h-2*load_m_h), c)
end
------------------------------------------------
-- Start of Cursor class
------------------------------------------------
Cursor = {
x_pos = 0,
y_pos = 0,
--color = 15,
h = 10,
w = nil,
line_w = nil,
is_cleared = true,
}
function Cursor:new(o)
o = o or {}
o.x_pos = o.x_pos or self.x_pos
o.y_pos = o.y_pos or self.y_pos
o.line_width_factor = o.line_width_factor or 10
setmetatable(o, self)
self.__index = self
o:setHeight(o.h or self.h)
return o
end
function Cursor:setHeight(h)
self.h = h
self.w = self.h / 3
self.line_w = math.floor(self.h / self.line_width_factor)
end
function Cursor:_draw(x, y)
local up_down_width = math.floor(self.line_w / 2)
local body_h = self.h - (up_down_width * 2)
-- paint upper horizontal line
fb.bb:invertRect(x, y, self.w, up_down_width)
-- paint middle vertical line
fb.bb:invertRect(x + (self.w / 2) - up_down_width, y + up_down_width,
self.line_w, body_h)
-- paint lower horizontal line
fb.bb:invertRect(x, y + body_h + up_down_width, self.w, up_down_width)
end
function Cursor:draw()
if self.is_cleared then
self.is_cleared = false
self:_draw(self.x_pos, self.y_pos)
end
end
function Cursor:clear()
if not self.is_cleared then
self.is_cleared = true
self:_draw(self.x_pos, self.y_pos)
end
end
function Cursor:move(x_off, y_off)
self.x_pos = self.x_pos + x_off
self.y_pos = self.y_pos + y_off
end
function Cursor:moveHorizontal(x_off)
self.x_pos = self.x_pos + x_off
end
function Cursor:moveVertical(x_off)
self.y_pos = self.y_pos + y_off
end
function Cursor:moveAndDraw(x_off, y_off)
self:clear()
self:move(x_off, y_off)
self:draw()
end
function Cursor:moveTo(x_pos, y_pos)
self.x_pos = x_pos
self.y_pos = y_pos
end
function Cursor:moveToAndDraw(x_pos, y_pos)
self:clear()
self.x_pos = x_pos
self.y_pos = y_pos
self:draw()
end
function Cursor:moveHorizontalAndDraw(x_off)
self:clear()
self:move(x_off, 0)
self:draw()
end
function Cursor:moveVerticalAndDraw(y_off)
self:clear()
self:move(0, y_off)
self:draw()
end

@ -7,22 +7,33 @@ require "selectmenu"
require "commands"
HelpPage = {
-- Other Class vars:
-- spacing between lines
spacing = 25,
-- state buffer
commands = nil,
items = 0,
page = 1
page = 1,
-- font for displaying keys
fsize = 20,
face = Font:getFace("hpkfont", 20),
-- font for displaying help messages
hfsize = 20,
hface = Font:getFace("hfont", 20),
-- font for paging display
ffsize = 15,
fface = Font:getFace("pgfont", 15)
}
-- Other Class vars:
-- font for displaying help messages
HelpPage.sFace, HelpPage.sHash = Font:getFaceAndHash(20, "sans")
-- font for displaying keys
HelpPage.mFace, HelpPage.mHash = Font:getFaceAndHash(20, "sans")
-- font for paging display
HelpPage.fFace, HelpPage.fHash = Font:getFaceAndHash(15, "sans")
function HelpPage:show(ypos,height,commands)
function HelpPage:show(ypos, height, commands)
self.commands = {}
self.items = 0
local keys = {}
@ -37,17 +48,17 @@ function HelpPage:show(ypos,height,commands)
end
table.sort(self.commands,function(w1,w2) return w1.order<w2.order end)
local mFaceHeight, mFaceAscender = self.mFace:getHeightAndAscender();
local fFaceHeight, fFaceAscender = self.fFace:getHeightAndAscender();
--print(mFaceHeight.."-"..mFaceAscender)
--print(fFaceHeight.."-"..fFaceAscender)
mFaceHeight = math.ceil(mFaceHeight)
mFaceAscender = math.ceil(mFaceAscender)
fFaceHeight = math.ceil(fFaceHeight)
fFaceAscender = math.ceil(fFaceAscender)
local spacing = mFaceHeight + 5
local faceHeight, faceAscender = self.face:getHeightAndAscender();
local ffaceHeight, ffaceAscender = self.fface:getHeightAndAscender();
--print(faceHeight.."-"..faceAscender)
--print(ffaceHeight.."-"..ffaceAscender)
faceHeight = math.ceil(faceHeight)
faceAscender = math.ceil(faceAscender)
ffaceHeight = math.ceil(ffaceHeight)
ffaceAscender = math.ceil(ffaceAscender)
local spacing = faceHeight + 5
local perpage = math.floor( (height - ypos - 1 * (fFaceHeight + 5)) / spacing )
local perpage = math.floor( (height - ypos - 1 * (ffaceHeight + 5)) / spacing )
local pagedirty = true
while true do
@ -62,19 +73,19 @@ function HelpPage:show(ypos,height,commands)
local key = self.commands[i].shortcut
for _k,aMod in pairs(MOD_TABLE) do
local modStart, modEnd = key:find(aMod.v)
print("key:"..key.." v:"..aMod.v.." d:"..aMod.d.." modstart:"..(modStart or "nil"))
--print("key:"..key.." v:"..aMod.v.." d:"..aMod.d.." modstart:"..(modStart or "nil"))
if(modStart ~= nil) then
key = key:sub(1,modStart-1)..key:sub(modEnd+1)
local box = sizeUtf8Text( x, fb.bb:getWidth(), self.mFace, self.mHash, aMod.d, true)
local box = sizeUtf8Text( x, fb.bb:getWidth(), self.face, aMod.d, true)
fb.bb:paintRect(x, ypos + spacing*c - box.y_top, box.x, box.y_top + box.y_bottom, 4);
local pen_x = renderUtf8Text(fb.bb, x, ypos + spacing*c, self.mFace, self.mHash, aMod.d.." + ", true)
local pen_x = renderUtf8Text(fb.bb, x, ypos + spacing*c, self.face, aMod.d.." + ", true)
x = x + pen_x
max_x = math.max(max_x, pen_x)
end
end
local box = sizeUtf8Text( x, fb.bb:getWidth(), self.mFace, self.mHash, key , true)
local box = sizeUtf8Text( x, fb.bb:getWidth(), self.face, key , true)
fb.bb:paintRect(x, ypos + spacing*c - box.y_top, box.x, box.y_top + box.y_bottom, 4);
local pen_x = renderUtf8Text(fb.bb, x, ypos + spacing*c, self.mFace, self.mHash, key, true)
local pen_x = renderUtf8Text(fb.bb, x, ypos + spacing*c, self.face, self.mHash, key, true)
x = x + pen_x
max_x = math.max(max_x, x)
end
@ -82,10 +93,10 @@ function HelpPage:show(ypos,height,commands)
for c = 1, perpage do
local i = (self.page - 1) * perpage + c
if i <= self.items then
renderUtf8Text(fb.bb, max_x + 20, ypos + spacing*c, self.sFace, self.sHash, self.commands[i].help, true)
renderUtf8Text(fb.bb, max_x + 20, ypos + spacing*c, self.hface, self.commands[i].help, true)
end
end
renderUtf8Text(fb.bb, 5, height - fFaceHeight + fFaceAscender - 5, self.fFace, self.fHash,
renderUtf8Text(fb.bb, 5, height - ffaceHeight + ffaceAscender - 5, self.fFace,
"Page "..self.page.." of "..math.ceil(self.items / perpage).." - click Back to close this page", true)
markerdirty = true
end
@ -94,7 +105,7 @@ function HelpPage:show(ypos,height,commands)
pagedirty = false
end
local ev = input.waitForEvent()
local ev = input.saveWaitForEvent()
--print("key code:"..ev.code)
ev.code = adjustKeyEvents(ev)
if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then

@ -0,0 +1,25 @@
Image = {}
function Image._getFileData(filename)
local f = io.open(filename)
local data = f:read("*a")
f:close()
return data
end
function Image.fromPNG(filename)
local img = mupdfimg.new()
img:loadPNGData(Image._getFileData(filename))
local bb = img:toBlitBuffer()
img:free()
return bb
end
function Image.fromJPEG(filename)
local img = mupdfimg.new()
img:loadJPEGData(Image._getFileData(filename)(fimgdatailename))
local bb = img:toBlitBuffer()
img:free()
return bb
end

@ -19,29 +19,100 @@
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <linux/input.h>
#include "input.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#define NUM_FDS 3
int inputfds[3] = { -1, -1, -1 };
#define OUTPUT_SIZE 21
#define CODE_IN_SAVER 10000
#define CODE_OUT_SAVER 10001
#define NUM_FDS 4
int inputfds[4] = { -1, -1, -1, -1 };
int slider_pid = -1;
int findFreeFdSlot() {
int i;
for(i=0; i<NUM_FDS; i++) {
if(inputfds[i] == -1) {
return i;
}
}
return -1;
}
static int openInputDevice(lua_State *L) {
#ifndef EMULATE_READER
int i;
int fd;
const char* inputdevice = luaL_checkstring(L, 1);
for(i=0; i<NUM_FDS; i++) {
if(inputfds[i] == -1) {
inputfds[i] = open(inputdevice, O_RDONLY | O_NONBLOCK, 0);
if(inputfds[i] != -1) {
ioctl(inputfds[i], EVIOCGRAB, 1);
return 0;
} else {
return luaL_error(L, "error opening input device <%s>: %d", inputdevice, errno);
fd = findFreeFdSlot();
if(fd == -1) {
return luaL_error(L, "no free slot for new input device <%s>", inputdevice);
}
if(!strcmp("slider",inputdevice)) {
/* special case: the power slider */
int pipefd[2];
int childpid;
pipe(pipefd);
if((childpid = fork()) == -1) {
return luaL_error(L, "cannot fork() slider event listener");
}
if(childpid == 0) {
FILE *fp;
char std_out[OUTPUT_SIZE] = "";
struct input_event ev;
int ret;
__u16 key_code = 10000;
close(pipefd[0]);
ev.type = EV_KEY;
ev.code = key_code;
ev.value = 1;
/* listen power slider events */
while(1) {
fp = popen("exec lipc-wait-event com.lab126.powerd goingToScreenSaver,outOfScreenSaver", "r");
if(fgets(std_out, OUTPUT_SIZE, fp) == NULL) {
break;
}
pclose(fp);
if(std_out[0] == 'g') {
ev.code = CODE_IN_SAVER;
} else if(std_out[0] == 'o') {
ev.code = CODE_OUT_SAVER;
} else {
printf("Unrecognized event.\n");
}
/* fill event struct */
gettimeofday(&ev.time, NULL);
/* generate event */
if(write(pipefd[1], &ev, sizeof(struct input_event)) == -1) {
break;
}
}
exit(0); /* cannot be reached?! */
} else {
close(pipefd[1]);
inputfds[fd] = pipefd[0];
slider_pid = childpid;
}
} else {
inputfds[fd] = open(inputdevice, O_RDONLY | O_NONBLOCK, 0);
if(inputfds[fd] != -1) {
ioctl(inputfds[fd], EVIOCGRAB, 1);
return 0;
} else {
return luaL_error(L, "error opening input device <%s>: %d", inputdevice, errno);
}
}
return luaL_error(L, "no free slot for new input device <%s>", inputdevice);
#else
if(SDL_Init(SDL_INIT_VIDEO) < 0) {
return luaL_error(L, "cannot initialize SDL.");
@ -58,6 +129,11 @@ static int closeInputDevices(lua_State *L) {
close(i);
}
}
if(slider_pid != -1) {
/* kill and wait for child process */
kill(slider_pid, SIGTERM);
waitpid(-1, NULL, 0);
}
return 0;
}
@ -66,7 +142,7 @@ static int waitForInput(lua_State *L) {
fd_set fds;
struct timeval timeout;
int i, num, nfds;
int usecs = luaL_optint(L, 1, 0x7FFFFFFF);
int usecs = luaL_optint(L, 1, -1); // we check for <0 later
timeout.tv_sec = (usecs/1000000);
timeout.tv_usec = (usecs%1000000);
@ -81,7 +157,11 @@ static int waitForInput(lua_State *L) {
nfds = inputfds[i] + 1;
}
num = select(nfds, &fds, NULL, NULL, &timeout);
/* when no value is given as argument, we pass
* NULL to select() for the timeout value, setting no
* timeout at all.
*/
num = select(nfds, &fds, NULL, NULL, (usecs < 0) ? NULL : &timeout);
if(num < 0) {
return luaL_error(L, "Waiting for input failed: %d\n", errno);
}

@ -1,9 +1,17 @@
require "font"
require "rendertext"
require "keys"
require "graphics"
----------------------------------------------------
-- General inputbox
----------------------------------------------------
InputBox = {
-- Class vars:
h = 100,
input_slot_w = nil,
input_start_x = 145,
input_start_y = nil,
input_cur_x = nil, -- points to the start of next input pos
@ -15,44 +23,103 @@ InputBox = {
shiftmode = false,
altmode = false,
cursor = nil,
-- font for displaying input content
face = freetype.newBuiltinFace("mono", 25),
fhash = "m25",
-- we have to use mono here for better distance controlling
face = Font:getFace("infont", 25),
fheight = 25,
fwidth = 16,
fwidth = 15,
commands = nil,
initialized = false,
}
function InputBox:setDefaultInput(text)
self.input_string = ""
self:addString(text)
--self.input_cur_x = self.input_start_x + (string.len(text) * self.fwidth)
--self.input_string = text
function InputBox:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function InputBox:addString(str)
for i = 1, #str do
self:addChar(str:sub(i,i))
function InputBox:init()
if not self.initialized then
self:addAllCommands()
self.initialized = true
end
end
function InputBox:refreshText()
-- clear previous painted text
fb.bb:paintRect(140, self.input_start_y-19,
self.input_slot_w, self.fheight, self.input_bg)
-- paint new text
renderUtf8Text(fb.bb, self.input_start_x, self.input_start_y,
self.face,
self.input_string, 0)
end
function InputBox:addChar(char)
renderUtf8Text(fb.bb, self.input_cur_x, self.input_start_y, self.face, self.fhash,
char, true)
fb:refresh(1, self.input_cur_x, self.input_start_y-19, self.fwidth, self.fheight)
self.cursor:clear()
-- draw new text
local cur_index = (self.cursor.x_pos + 3 - self.input_start_x)
/ self.fwidth
self.input_string = self.input_string:sub(1, cur_index)..char..
self.input_string:sub(cur_index+1)
self:refreshText()
self.input_cur_x = self.input_cur_x + self.fwidth
self.input_string = self.input_string .. char
-- draw new cursor
self.cursor:moveHorizontal(self.fwidth)
self.cursor:draw()
fb:refresh(1, self.input_start_x-5, self.input_start_y-25,
self.input_slot_w, self.h-25)
end
function InputBox:delChar()
if self.input_start_x == self.input_cur_x then
return
end
local cur_index = (self.cursor.x_pos + 3 - self.input_start_x)
/ self.fwidth
if cur_index == 0 then return end
self.cursor:clear()
-- draw new text
self.input_string = self.input_string:sub(1, cur_index-1)..
self.input_string:sub(cur_index+1, -1)
self:refreshText()
self.input_cur_x = self.input_cur_x - self.fwidth
--fill last character with blank rectangle
fb.bb:paintRect(self.input_cur_x, self.input_start_y-19,
self.fwidth, self.fheight, self.input_bg)
fb:refresh(1, self.input_cur_x, self.input_start_y-19, self.fwidth, self.fheight)
self.input_string = self.input_string:sub(0,-2)
-- draw new cursor
self.cursor:moveHorizontal(-self.fwidth)
self.cursor:draw()
fb:refresh(1, self.input_start_x-5, self.input_start_y-25,
self.input_slot_w, self.h-25)
end
function InputBox:clearText()
self.cursor:clear()
self.input_string = ""
self:refreshText()
self.cursor.x_pos = self.input_start_x - 3
self.cursor:draw()
fb:refresh(1, self.input_start_x-5, self.input_start_y-25,
self.input_slot_w, self.h-25)
end
function InputBox:drawHelpMsg(ypos, w, h)
return
end
function InputBox:drawBox(ypos, w, h, title)
@ -61,139 +128,262 @@ function InputBox:drawBox(ypos, w, h, title)
-- draw input slot
fb.bb:paintRect(140, ypos + 10, w - 130, h - 20, self.input_bg)
-- draw input title
renderUtf8Text(fb.bb, 35, self.input_start_y, self.face, self.fhash,
renderUtf8Text(fb.bb, 35, self.input_start_y, self.face,
title, true)
end
--[[
|| d_text default to nil (used to set default text in input slot)
--]]
----------------------------------------------------------------------
-- InputBox:input()
--
-- @title: input prompt for the box
-- @d_text: default to nil (used to set default text in input slot)
----------------------------------------------------------------------
function InputBox:input(ypos, height, title, d_text)
local pagedirty = true
self:init()
-- do some initilization
self.ypos = ypos
self.h = height
self.input_start_y = ypos + 35
self.input_cur_x = self.input_start_x
self.input_slot_w = fb.bb:getWidth() - 170
self.cursor = Cursor:new {
x_pos = self.input_start_x - 3,
y_pos = ypos + 13,
h = 30,
}
if d_text then -- if specified default text, draw it
w = fb.bb:getWidth() - 40
h = height - 45
self:drawBox(ypos, w, h, title)
self:setDefaultInput(d_text)
fb:refresh(1, 20, ypos, w, h)
pagedirty = false
else -- otherwise, leave the draw task to the main loop
self.input_string = ""
-- draw box and content
w = fb.bb:getWidth() - 40
h = height - 45
self:drawHelpMsg(ypos, w, h)
self:drawBox(ypos, w, h, title)
if d_text then
self.input_string = d_text
self.input_cur_x = self.input_cur_x + (self.fwidth * d_text:len())
self.cursor.x_pos = self.cursor.x_pos + (self.fwidth * d_text:len())
self:refreshText()
end
self.cursor:draw()
fb:refresh(1, 20, ypos, w, h)
while true do
if pagedirty then
w = fb.bb:getWidth() - 40
h = height - 45
self:drawBox(ypos, w, h, title)
fb:refresh(1, 20, ypos, w, h)
pagedirty = false
end
local ev = input.waitForEvent()
local ev = input.saveWaitForEvent()
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_FW_UP then
elseif ev.code == KEY_FW_DOWN then
elseif ev.code == KEY_A then
self:addChar("a")
elseif ev.code == KEY_B then
self:addChar("b")
elseif ev.code == KEY_C then
self:addChar("c")
elseif ev.code == KEY_D then
self:addChar("d")
elseif ev.code == KEY_E then
self:addChar("e")
elseif ev.code == KEY_F then
self:addChar("f")
elseif ev.code == KEY_G then
self:addChar("g")
elseif ev.code == KEY_H then
self:addChar("h")
elseif ev.code == KEY_I then
self:addChar("i")
elseif ev.code == KEY_J then
self:addChar("j")
elseif ev.code == KEY_K then
self:addChar("k")
elseif ev.code == KEY_L then
self:addChar("l")
elseif ev.code == KEY_M then
self:addChar("m")
elseif ev.code == KEY_N then
self:addChar("n")
elseif ev.code == KEY_O then
self:addChar("o")
elseif ev.code == KEY_P then
self:addChar("p")
elseif ev.code == KEY_Q then
self:addChar("q")
elseif ev.code == KEY_R then
self:addChar("r")
elseif ev.code == KEY_S then
self:addChar("s")
elseif ev.code == KEY_T then
self:addChar("t")
elseif ev.code == KEY_U then
self:addChar("u")
elseif ev.code == KEY_V then
self:addChar("v")
elseif ev.code == KEY_W then
self:addChar("w")
elseif ev.code == KEY_X then
self:addChar("x")
elseif ev.code == KEY_Y then
self:addChar("y")
elseif ev.code == KEY_Z then
self:addChar("z")
elseif ev.code == KEY_1 then
self:addChar("1")
elseif ev.code == KEY_2 then
self:addChar("2")
elseif ev.code == KEY_3 then
self:addChar("3")
elseif ev.code == KEY_4 then
self:addChar("4")
elseif ev.code == KEY_5 then
self:addChar("5")
elseif ev.code == KEY_6 then
self:addChar("6")
elseif ev.code == KEY_7 then
self:addChar("7")
elseif ev.code == KEY_8 then
self:addChar("8")
elseif ev.code == KEY_9 then
self:addChar("9")
elseif ev.code == KEY_0 then
self:addChar("0")
elseif ev.code == KEY_SPACE then
self:addChar(" ")
elseif ev.code == KEY_PGFWD then
elseif ev.code == KEY_PGBCK then
elseif ev.code == KEY_ENTER or ev.code == KEY_FW_PRESS then
if self.input_string == "" then
self.input_string = nil
end
break
elseif ev.code == KEY_DEL then
self:delChar()
elseif ev.code == KEY_BACK then
self.input_string = nil
break
keydef = Keydef:new(ev.code, getKeyModifier())
print("key pressed: "..tostring(keydef))
command = self.commands:getByKeydef(keydef)
if command ~= nil then
print("command to execute: "..tostring(command))
ret_code = command.func(self, keydef)
else
print("command not found: "..tostring(command))
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)
if ret_code == "break" then
ret_code = nil
break
end
end -- if
end -- while
return self.input_string
local return_str = self.input_string
self.input_string = ""
return return_str
end
function InputBox:addAllCommands()
if self.commands then
-- we only initialize once
return
end
self.commands = Commands:new{}
INPUT_KEYS = {
{KEY_Q, "q"}, {KEY_W, "w"}, {KEY_E, "e"}, {KEY_R, "r"}, {KEY_T, "t"},
{KEY_Y, "y"}, {KEY_U, "u"}, {KEY_I, "i"}, {KEY_O, "o"}, {KEY_P, "p"},
{KEY_A, "a"}, {KEY_S, "s"}, {KEY_D, "d"}, {KEY_F, "f"}, {KEY_G, "g"},
{KEY_H, "h"}, {KEY_J, "j"}, {KEY_K, "k"}, {KEY_L, "l"},
{KEY_Z, "z"}, {KEY_X, "x"}, {KEY_C, "c"}, {KEY_V, "v"}, {KEY_B, "b"},
{KEY_N, "n"}, {KEY_M, "m"},
{KEY_1, "1"}, {KEY_2, "2"}, {KEY_3, "3"}, {KEY_4, "4"}, {KEY_5, "5"},
{KEY_6, "6"}, {KEY_7, "7"}, {KEY_8, "8"}, {KEY_9, "9"}, {KEY_0, "0"},
{KEY_SPACE, " "},
-- DXG keys
{KEY_DOT, "."}, {KEY_SLASH, "/"},
}
for k,v in ipairs(INPUT_KEYS) do
self.commands:add(v[1], nil, "",
"input "..v[2],
function(self)
self:addChar(v[2])
end
)
end
self.commands:add(KEY_FW_LEFT, nil, "",
"move cursor left",
function(self)
if (self.cursor.x_pos + 3) > self.input_start_x then
self.cursor:moveHorizontalAndDraw(-self.fwidth)
fb:refresh(1, self.input_start_x-5, self.ypos,
self.input_slot_w, self.h)
end
end
)
self.commands:add(KEY_FW_RIGHT, nil, "",
"move cursor right",
function(self)
if (self.cursor.x_pos + 3) < self.input_cur_x then
self.cursor:moveHorizontalAndDraw(self.fwidth)
fb:refresh(1, self.input_start_x-5, self.ypos,
self.input_slot_w, self.h)
end
end
)
self.commands:add({KEY_ENTER, KEY_FW_PRESS}, nil, "",
"submit input content",
function(self)
if self.input_string == "" then
self.input_string = nil
end
return "break"
end
)
self.commands:add(KEY_DEL, nil, "",
"delete one character",
function(self)
self:delChar()
end
)
self.commands:add(KEY_DEL, MOD_SHIFT, "",
"empty inputbox",
function(self)
self:clearText()
end
)
self.commands:add({KEY_BACK, KEY_HOME}, nil, "",
"cancel inputbox",
function(self)
self.input_string = nil
return "break"
end
)
end
----------------------------------------------------
-- Inputbox for numbers only
-- Designed by eLiNK
----------------------------------------------------
NumInputBox = InputBox:new{
initialized = false,
commands = Commands:new{},
}
function NumInputBox:addAllCommands()
self.commands = Commands:new{}
INPUT_NUM_KEYS = {
{KEY_Q, "1"}, {KEY_W, "2"}, {KEY_E, "3"}, {KEY_R, "4"}, {KEY_T, "5"},
{KEY_Y, "6"}, {KEY_U, "7"}, {KEY_I, "8"}, {KEY_O, "9"}, {KEY_P, "0"},
{KEY_1, "1"}, {KEY_2, "2"}, {KEY_3, "3"}, {KEY_4, "4"}, {KEY_5, "5"},
{KEY_6, "6"}, {KEY_7, "7"}, {KEY_8, "8"}, {KEY_9, "9"}, {KEY_0, "0"},
}
for k,v in ipairs(INPUT_NUM_KEYS) do
self.commands:add(v[1], nil, "",
"input "..v[2],
function(self)
self:addChar(v[2])
end
)
end -- for
self.commands:add(KEY_FW_LEFT, nil, "",
"move cursor left",
function(self)
if (self.cursor.x_pos + 3) > self.input_start_x then
self.cursor:moveHorizontalAndDraw(-self.fwidth)
fb:refresh(1, self.input_start_x-5, self.ypos,
self.input_slot_w, self.h)
end
end
)
self.commands:add(KEY_FW_RIGHT, nil, "",
"move cursor right",
function(self)
if (self.cursor.x_pos + 3) < self.input_cur_x then
self.cursor:moveHorizontalAndDraw(self.fwidth)
fb:refresh(1, self.input_start_x-5, self.ypos,
self.input_slot_w, self.h)
end
end
)
self.commands:add({KEY_ENTER, KEY_FW_PRESS}, nil, "",
"submit input content",
function(self)
if self.input_string == "" then
self.input_string = nil
end
return "break"
end
)
self.commands:add(KEY_DEL, nil, "",
"delete one character",
function(self)
self:delChar()
end
)
self.commands:add(KEY_DEL, MOD_SHIFT, "",
"empty inputbox",
function(self)
self:clearText()
end
)
self.commands:add({KEY_BACK, KEY_HOME}, nil, "",
"cancel inputbox",
function(self)
self.input_string = nil
return "break"
end
)
end
function NumInputBox:drawHelpMsg(ypos, w, h)
local w = 415
local y = ypos - 60
local x = (G_width - w) / 2
local h = 50
local bw = 2
local face = Font:getFace("scfont", 22)
fb.bb:paintRect(x, y, w, h, 15)
fb.bb:paintRect(x+bw, y+bw, w-2*bw, h-2*bw, 0)
local font_y = y + 22
local font_x = x + 22
INPUT_NUM_KEYS = {
{"Q", "1"}, {"W", "2"}, {"E", "3"}, {"R", "4"}, {"T", "5"},
{"Y", "6"}, {"U", "7"}, {"I", "8"}, {"O", "9"}, {"P", "0"},
}
for k,v in ipairs(INPUT_NUM_KEYS) do
renderUtf8Text(fb.bb, font_x, font_y, face,
v[1], true)
renderUtf8Text(fb.bb, font_x, font_y + 22, face,
v[2], true)
font_x = font_x + 40
end
fb:refresh(1, x, y, w, h)
end

@ -87,6 +87,9 @@ KEY_FW_UP = 122
KEY_FW_DOWN = 123
KEY_FW_PRESS = 92
KEY_INTO_SCREEN_SAVER = 10000
KEY_OUTOF_SCREEN_SAVER = 10001
-- constants from <linux/input.h>
EV_KEY = 1
@ -126,6 +129,8 @@ end
function setEmuKeycodes()
KEY_PGFWD = 117
KEY_PGBCK = 112
KEY_LPGBCK = 69 -- F3
KEY_LPGFWD = 70 -- F4
KEY_HOME = 110 -- home
KEY_BACK = 22 -- backspace
KEY_DEL = 119 -- Delete
@ -252,3 +257,22 @@ function adjustKeyEvents(ev)
print("# Unrecognizable rotation mode "..Screen.cur_rotation_mode.."!")
return nil
end
-- wrapper for input.waitForEvents that will retry for some cases
function input.saveWaitForEvent(timeout)
local retry = true
while retry do
local ok, ev = pcall(input.waitForEvent, timeout)
if not ok then
print("got error waiting for events:", ev)
if ev == "Waiting for input failed: 4\n" then
-- EINTR, we got interrupted. Try and restart
retry = true
else
retry = false
end
else
return ev
end
end
end

@ -26,6 +26,9 @@
#include "blitbuffer.h"
#include "drawcontext.h"
#include "pdf.h"
#include "mupdfimg.h"
#include "djvu.h"
#include "cre.h"
#include "einkfb.h"
#include "input.h"
#include "ft.h"
@ -53,9 +56,11 @@ int main(int argc, char **argv) {
luaopen_einkfb(L);
luaopen_pdf(L);
luaopen_djvu(L);
luaopen_cre(L);
luaopen_input(L);
luaopen_util(L);
luaopen_ft(L);
luaopen_mupdfimg(L);
luaopen_lfs(L);

@ -0,0 +1,57 @@
PROJECT(kpvcrlib)
cmake_minimum_required(VERSION 2.6)
SET(MUPDF_DIR ../mupdf)
SET(MUPDF_3RDPARTY_DIR ${MUPDF_DIR}/thirdparty)
SET(CR_3RDPARTY_DIR crengine/thirdparty)
SET(CR3_PNG 1)
#SET(CR3_JPEG 1)
SET(FREETYPE_INCLUDE_DIRS ${MUPDF_3RDPARTY_DIR}/freetype-2.4.8/include)
#SET(FREETYPE_INCLUDE_DIRS ${CR_3RDPARTY_DIR}/freetype/include)
SET(ANTIWORD_INCLUDE_DIR ${CR_3RDPARTY_DIR}/antiword)
SET(CHM_INCLUDE_DIRS ${CR_3RDPARTY_DIR}/chmlib)
SET(PNG_INCLUDE_DIR ${CR_3RDPARTY_DIR}/libpng)
SET(ZLIB_INCLUDE_DIR ${MUPDF_3RDPARTY_DIR}/zlib-1.2.5)
#SET(ZLIB_INCLUDE_DIR ${CR_3RDPARTY_DIR}/zlib)
SET(JPEGLIB_INCLUDE_DIR ${MUPDF_3RDPARTY_DIR}/jpeg-8d)
#SET(JPEGLIB_INCLUDE_DIR ${CR_3RDPARTY_DIR}/libjpeg)
SET(JCONFIG_INCLUDE_DIR ${MUPDF_DIR}/scripts)
INCLUDE_DIRECTORIES(
${FREETYPE_INCLUDE_DIRS}
${ANTIWORD_INCLUDE_DIR}
${CHM_INCLUDE_DIRS}
${PNG_INCLUDE_DIR}
${ZLIB_INCLUDE_DIR}
${JPEGLIB_INCLUDE_DIR}
${JCONFIG_INCLUDE_DIR}
)
ADD_DEFINITIONS(-DLINUX=1 -D_LINUX=1 -DUSE_FONTCONFIG=0 -DUSE_FREETYPE=1 -DCR3_PATCH=1 -DNDEBUG=1)
message("Will build patched LIBCHM library")
ADD_DEFINITIONS(-DCHM_SUPPORT_ENABLED=1)
ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/chmlib)
message("Will build patched LIBPNG library")
ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/libpng)
#message("Will build patched JPEGLIB library")
#ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/libjpeg)
message("Will not build patched ANTIWORD library, because we haven't supported dictionary lookup yet.")
#message("Will build patched ANTIWORD library")
ADD_DEFINITIONS(-DENABLE_ANTIWORD=0)
#ADD_DEFINITIONS(-DCR3_ANTIWORD_PATCH=1)
#ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/antiword)
message("Will build crengine library")
SET(GUI kpv)
#ADD_DEFINITIONS(-DJCONFIG_INCLUDED=1)
ADD_SUBDIRECTORY(crengine/crengine)

@ -0,0 +1 @@
Subproject commit ba469d33473670ca303e2ef7f9762452a86e18b1

@ -0,0 +1,15 @@
--- jcapimin.c 2012-04-04 00:02:30.000000000 +0800
+++ jcapimin-patched.c 2012-04-04 00:02:26.000000000 +0800
@@ -36,9 +36,9 @@
cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */
if (version != JPEG_LIB_VERSION)
ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);
- if (structsize != SIZEOF(struct jpeg_compress_struct))
- ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE,
- (int) SIZEOF(struct jpeg_compress_struct), (int) structsize);
+ /*if (structsize != SIZEOF(struct jpeg_compress_struct))*/
+ /*ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, */
+ /*(int) SIZEOF(struct jpeg_compress_struct), (int) structsize);*/
/* For debugging purposes, we zero the whole master structure.
* But the application has already set the err pointer, and may have set

@ -0,0 +1,15 @@
--- jdapimin.c 2012-04-04 01:09:00.000000000 +0800
+++ jdapimin-patched.c 2012-04-04 01:42:44.000000000 +0800
@@ -36,9 +36,9 @@
cinfo->mem = NULL; /* so jpeg_destroy knows mem mgr not called */
if (version != JPEG_LIB_VERSION)
ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);
- if (structsize != SIZEOF(struct jpeg_decompress_struct))
- ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE,
- (int) SIZEOF(struct jpeg_decompress_struct), (int) structsize);
+ /*if (structsize != SIZEOF(struct jpeg_decompress_struct))*/
+ /*ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE, */
+ /*(int) SIZEOF(struct jpeg_decompress_struct), (int) structsize);*/
/* For debugging purposes, we zero the whole master structure.
* But the application has already set the err pointer, and may have set

@ -1,3 +1,7 @@
[Actions]
P P = !/mnt/us/launchpad/kpdf.sh
# start kindlepdfviewer with filebrowser in /mnt/us/documents
P D = !/mnt/us/launchpad/kpdf.sh /mnt/us/documents
# start kindlepdfviewer with last document
P P = !/mnt/us/launchpad/kpdf.sh
# restart amazon framework - when it got irritated
P R = !/etc/init.d/framework restart

@ -3,6 +3,14 @@ export LC_ALL="en_US.UTF-8"
echo unlock > /proc/keypad
echo unlock > /proc/fiveway
cd /mnt/us/kindlepdfviewer/
./reader.lua $1
grep /mnt/us/kindlepdfviewer/fonts/host /proc/mounts || mount -o bind /usr/java/lib/fonts /mnt/us/kindlepdfviewer/fonts/host
./reader.lua "$1" 2> /mnt/us/kindlepdfviewer/crash.log || cat /mnt/us/kindlepdfviewer/crash.log
grep /mnt/us/kindlepdfviewer/fonts/host /proc/mounts && umount /mnt/us/kindlepdfviewer/fonts/host
killall -cont cvm
echo 1 > /proc/eink_fb/update_display

@ -0,0 +1,171 @@
diff --git a/pdf/pdf_font.c b/pdf/pdf_font.c
index 5e54e0b..38bd1d8 100644
--- a/pdf/pdf_font.c
+++ b/pdf/pdf_font.c
@@ -182,8 +182,13 @@ pdf_load_builtin_font(fz_context *ctx, pdf_font_desc *fontdesc, char *fontname)
if (!data)
fz_throw(ctx, "cannot find builtin font: '%s'", fontname);
+#ifndef NOBUILTINFONT
fontdesc->font = fz_new_font_from_memory(ctx, data, len, 0, 1);
/* RJW: "cannot load freetype font from memory" */
+#else
+ fontdesc->font = fz_new_font_from_file(ctx, data, 0, 1);
+ free(data);
+#endif
if (!strcmp(fontname, "Symbol") || !strcmp(fontname, "ZapfDingbats"))
fontdesc->flags |= PDF_FD_SYMBOLIC;
@@ -199,8 +204,13 @@ pdf_load_substitute_font(fz_context *ctx, pdf_font_desc *fontdesc, int mono, int
if (!data)
fz_throw(ctx, "cannot find substitute font");
+#ifndef NOBUILTINFONT
fontdesc->font = fz_new_font_from_memory(ctx, data, len, 0, 1);
/* RJW: "cannot load freetype font from memory" */
+#else
+ fontdesc->font = fz_new_font_from_file(ctx, data, 0, 1);
+ free(data);
+#endif
fontdesc->font->ft_substitute = 1;
fontdesc->font->ft_bold = bold && !ft_is_bold(fontdesc->font->ft_face);
@@ -218,7 +228,12 @@ pdf_load_substitute_cjk_font(fz_context *ctx, pdf_font_desc *fontdesc, int ros,
fz_throw(ctx, "cannot find builtin CJK font");
/* a glyph bbox cache is too big for droid sans fallback (51k glyphs!) */
+#ifndef NOBUILTINFONT
fontdesc->font = fz_new_font_from_memory(ctx, data, len, 0, 0);
+#else
+ fontdesc->font = fz_new_font_from_file(ctx, data, 0, 0);
+ free(data);
+#endif
/* RJW: "cannot load builtin CJK font" */
fontdesc->font->ft_substitute = 1;
diff --git a/pdf/pdf_fontfile.c b/pdf/pdf_fontfile.c
index 543ce76..a076033 100644
--- a/pdf/pdf_fontfile.c
+++ b/pdf/pdf_fontfile.c
@@ -1,6 +1,8 @@
#include "fitz.h"
#include "mupdf.h"
+#ifndef NOBUILTINFONT
+
#ifdef NOCJK
#define NOCJKFONT
#endif
@@ -129,3 +131,112 @@ pdf_find_substitute_cjk_font(int ros, int serif, unsigned int *len)
return NULL;
#endif
}
+
+#else // NOBUILTINFONT
+
+unsigned char *
+get_font_file(char *name)
+{
+ char *fontdir;
+ char *filename;
+ int len;
+ fontdir = getenv("FONTDIR");
+ if(fontdir == NULL) {
+ fontdir = "./fonts";
+ }
+ len = strlen(fontdir) + strlen(name) + 2;
+ filename = malloc(len);
+ if(filename == NULL) {
+ return NULL;
+ }
+ snprintf(filename, len, "%s/%s", fontdir, name);
+ return filename;
+}
+
+unsigned char *
+pdf_find_builtin_font(char *name, unsigned int *len)
+{
+ *len = 0;
+ if (!strcmp("Courier", name)) {
+ return get_font_file("NimbusMonL-Regu.cff");
+ }
+ if (!strcmp("Courier-Bold", name)) {
+ return get_font_file("NimbusMonL-Bold.cff");
+ }
+ if (!strcmp("Courier-Oblique", name)) {
+ return get_font_file("NimbusMonL-ReguObli.cff");
+ }
+ if (!strcmp("Courier-BoldOblique", name)) {
+ return get_font_file("NimbusMonL-BoldObli.cff");
+ }
+ if (!strcmp("Helvetica", name)) {
+ return get_font_file("NimbusSanL-Regu.cff");
+ }
+ if (!strcmp("Helvetica-Bold", name)) {
+ return get_font_file("NimbusSanL-Bold.cff");
+ }
+ if (!strcmp("Helvetica-Oblique", name)) {
+ return get_font_file("NimbusSanL-ReguItal.cff");
+ }
+ if (!strcmp("Helvetica-BoldOblique", name)) {
+ return get_font_file("NimbusSanL-BoldItal.cff");
+ }
+ if (!strcmp("Times-Roman", name)) {
+ return get_font_file("NimbusRomNo9L-Regu.cff");
+ }
+ if (!strcmp("Times-Bold", name)) {
+ return get_font_file("NimbusRomNo9L-Medi.cff");
+ }
+ if (!strcmp("Times-Italic", name)) {
+ return get_font_file("NimbusRomNo9L-ReguItal.cff");
+ }
+ if (!strcmp("Times-BoldItalic", name)) {
+ return get_font_file("NimbusRomNo9L-MediItal.cff");
+ }
+ if (!strcmp("Symbol", name)) {
+ return get_font_file("StandardSymL.cff");
+ }
+ if (!strcmp("ZapfDingbats", name)) {
+ return get_font_file("Dingbats.cff");
+ }
+ return NULL;
+}
+
+unsigned char *
+pdf_find_substitute_font(int mono, int serif, int bold, int italic, unsigned int *len)
+{
+ if (mono) {
+ if (bold) {
+ if (italic) return pdf_find_builtin_font("Courier-BoldOblique", len);
+ else return pdf_find_builtin_font("Courier-Bold", len);
+ } else {
+ if (italic) return pdf_find_builtin_font("Courier-Oblique", len);
+ else return pdf_find_builtin_font("Courier", len);
+ }
+ } else if (serif) {
+ if (bold) {
+ if (italic) return pdf_find_builtin_font("Times-BoldItalic", len);
+ else return pdf_find_builtin_font("Times-Bold", len);
+ } else {
+ if (italic) return pdf_find_builtin_font("Times-Italic", len);
+ else return pdf_find_builtin_font("Times-Roman", len);
+ }
+ } else {
+ if (bold) {
+ if (italic) return pdf_find_builtin_font("Helvetica-BoldOblique", len);
+ else return pdf_find_builtin_font("Helvetica-Bold", len);
+ } else {
+ if (italic) return pdf_find_builtin_font("Helvetica-Oblique", len);
+ else return pdf_find_builtin_font("Helvetica", len);
+ }
+ }
+}
+
+unsigned char *
+pdf_find_substitute_cjk_font(int ros, int serif, unsigned int *len)
+{
+ *len = 0;
+ return get_font_file("droid/DroidSansFallback.ttf");
+}
+
+#endif // NOBUILTINFONT

@ -0,0 +1,154 @@
/*
KindlePDFViewer: MuPDF abstraction for Lua, only image part
Copyright (C) 2012 Hans-Werner Hilse <hilse@web.de>
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 <http://www.gnu.org/licenses/>.
*/
#include <fitz/fitz.h>
#include "blitbuffer.h"
#include "mupdfimg.h"
#include <stdio.h>
#include <stddef.h>
typedef struct Image {
fz_pixmap *pixmap;
fz_context *context;
} Image;
static int newImage(lua_State *L) {
int cache_size = luaL_optint(L, 1, 8 << 20); // 8 MB limit default
Image *img = (Image*) lua_newuserdata(L, sizeof(Image));
img->pixmap = NULL;
luaL_getmetatable(L, "image");
lua_setmetatable(L, -2);
img->context = fz_new_context(NULL, NULL, cache_size);
return 1;
}
static int loadPNGData(lua_State *L) {
Image *img = (Image*) luaL_checkudata(L, 1, "image");
size_t length;
unsigned char *data = luaL_checklstring(L, 2, &length);
fz_try(img->context) {
img->pixmap = fz_load_png(img->context, data, length);
}
fz_catch(img->context) {
return luaL_error(L, "cannot load PNG data");
}
}
static int loadJPEGData(lua_State *L) {
Image *img = (Image*) luaL_checkudata(L, 1, "image");
size_t length;
unsigned char *data = luaL_checklstring(L, 2, &length);
fz_try(img->context) {
img->pixmap = fz_load_jpeg(img->context, data, length);
}
fz_catch(img->context) {
return luaL_error(L, "cannot open JPEG data");
}
}
static int toBlitBuffer(lua_State *L) {
Image *img = (Image*) luaL_checkudata(L, 1, "image");
BlitBuffer *bb;
int ret;
int w, h;
fz_pixmap *pix;
if(img->pixmap == NULL) {
return luaL_error(L, "no pixmap loaded that we could convert");
}
if(img->pixmap->n == 2) {
pix = img->pixmap;
} else {
fz_try(img->context) {
pix = fz_new_pixmap(img->context, fz_device_gray, img->pixmap->w, img->pixmap->h);
}
fz_catch(img->context) {
return luaL_error(L, "can't claim new grayscale fz_pixmap");
}
fz_convert_pixmap(img->context, img->pixmap, pix);
}
ret = newBlitBufferNative(L, img->pixmap->w, img->pixmap->h, &bb);
if(ret != 1) {
// TODO (?): fail more gracefully, clean up mem?
return ret;
}
uint8_t *bbptr = (uint8_t*)bb->data;
uint16_t *pmptr = (uint16_t*)pix->samples;
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) ^ 0xF0;
}
bbptr += bb->pitch;
pmptr += bb->w;
}
if(pix != img->pixmap) {
fz_drop_pixmap(img->context, pix);
}
return 1;
}
static int freeImage(lua_State *L) {
Image *img = (Image*) luaL_checkudata(L, 1, "image");
if(img->pixmap) {
fz_drop_pixmap(img->context, img->pixmap);
}
fz_free_context(img->context);
return 0;
}
static const struct luaL_Reg mupdfimg_func[] = {
{"new", newImage},
{NULL, NULL}
};
static const struct luaL_Reg image_meth[] = {
{"loadPNGData", loadPNGData},
{"loadJPEGData", loadJPEGData},
{"toBlitBuffer", toBlitBuffer},
{"free", freeImage},
{"__gc", freeImage},
{NULL, NULL}
};
int luaopen_mupdfimg(lua_State *L) {
luaL_newmetatable(L, "image");
lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3);
luaL_register(L, NULL, image_meth);
lua_pop(L, 1);
luaL_register(L, "mupdfimg", mupdfimg_func);
return 1;
}

@ -0,0 +1,26 @@
/*
KindlePDFViewer: MuPDF abstraction for Lua, only image part
Copyright (C) 2012 Hans-Werner Hilse <hilse@web.de>
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 <http://www.gnu.org/licenses/>.
*/
#ifndef _MUPDFIMG_H
#define _MUPDFIMG_H
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int luaopen_mupdfimg(lua_State *L);
#endif

143
pdf.c

@ -164,16 +164,16 @@ fz_alloc_context my_alloc_default =
static int openDocument(lua_State *L) {
char *filename = strdup(luaL_checkstring(L, 1));
int cachesize = luaL_optint(L, 2, 64 << 20); // 64 MB limit default
int cache_size = luaL_optint(L, 2, 64 << 20); // 64 MB limit default
char buf[15];
printf("cachesize: %s\n",readable_fs(cachesize,buf));
printf("## cache_size: %s\n",readable_fs(cache_size,buf));
PdfDocument *doc = (PdfDocument*) lua_newuserdata(L, sizeof(PdfDocument));
luaL_getmetatable(L, "pdfdocument");
lua_setmetatable(L, -2);
doc->context = fz_new_context(&my_alloc_default, NULL, cachesize);
doc->context = fz_new_context(&my_alloc_default, NULL, cache_size);
fz_try(doc->context) {
doc->xref = fz_open_document(doc->context, filename);
@ -311,6 +311,125 @@ static int openPage(lua_State *L) {
return 1;
}
/* get the text of the given page
*
* will return text in a Lua table that is modeled after
* djvu.c creates this table.
*
* note that the definition of "line" is somewhat arbitrary
* here (for now)
*
* MuPDFs API provides text as single char information
* that is collected in "spans". we use a span as a "line"
* in Lua output and segment spans into words by looking
* for space characters.
*
* will return an empty table if we have no text
*/
static int getPageText(lua_State *L) {
fz_text_span *page_text;
fz_text_span *ptr;
fz_device *tdev;
fz_bbox bbox, linebbox;
int i;
int word, line;
int len, c;
int start;
char chars[4]; // max length of UTF-8 encoded rune
luaL_Buffer textbuf;
PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage");
page_text = fz_new_text_span(page->doc->context);
tdev = fz_new_text_device(page->doc->context, page_text);
fz_run_page(page->doc->xref, page->page, tdev, fz_identity, NULL);
fz_free_device(tdev);
/* table that contains all the lines */
lua_newtable(L);
line = 1;
for(ptr = page_text; ptr != NULL; ptr = ptr->next) {
if(ptr->text == NULL) continue;
/* table for the words */
lua_newtable(L);
word = 1;
linebbox = ptr->text[0].bbox; // start with sensible default
for(i = 0; i < ptr->len; ) {
/* will hold information about a word: */
lua_newtable(L);
luaL_buffinit(L, &textbuf);
bbox = ptr->text[i].bbox; // start with sensible default
for(; i < ptr->len; i++) {
/* check for space characters */
if(ptr->text[i].c == ' ' ||
ptr->text[i].c == '\t' ||
ptr->text[i].c == '\n' ||
ptr->text[i].c == '\v' ||
ptr->text[i].c == '\f' ||
ptr->text[i].c == '\r' ||
ptr->text[i].c == 0xA0 ||
ptr->text[i].c == 0x1680 ||
ptr->text[i].c == 0x180E ||
(ptr->text[i].c >= 0x2000 && ptr->text[i].c <= 0x200A) ||
ptr->text[i].c == 0x202F ||
ptr->text[i].c == 0x205F ||
ptr->text[i].c == 0x3000) {
// ignore and end word
i++;
break;
}
len = runetochar(chars, &ptr->text[i].c);
for(c = 0; c < len; c++) {
luaL_addchar(&textbuf, chars[c]);
}
bbox = fz_union_bbox(bbox, ptr->text[i].bbox);
linebbox = fz_union_bbox(linebbox, ptr->text[i].bbox);
}
lua_pushstring(L, "word");
luaL_pushresult(&textbuf);
lua_settable(L, -3);
/* bbox for a word: */
lua_pushstring(L, "x0");
lua_pushinteger(L, bbox.x0);
lua_settable(L, -3);
lua_pushstring(L, "y0");
lua_pushinteger(L, bbox.y0);
lua_settable(L, -3);
lua_pushstring(L, "x1");
lua_pushinteger(L, bbox.x1);
lua_settable(L, -3);
lua_pushstring(L, "y1");
lua_pushinteger(L, bbox.y1);
lua_settable(L, -3);
lua_rawseti(L, -2, word++);
}
/* bbox for a whole line (or in fact, a "span") */
lua_pushstring(L, "x0");
lua_pushinteger(L, linebbox.x0);
lua_settable(L, -3);
lua_pushstring(L, "y0");
lua_pushinteger(L, linebbox.y0);
lua_settable(L, -3);
lua_pushstring(L, "x1");
lua_pushinteger(L, linebbox.x1);
lua_settable(L, -3);
lua_pushstring(L, "y1");
lua_pushinteger(L, linebbox.y1);
lua_settable(L, -3);
lua_rawseti(L, -2, line++);
}
fz_free_text_span(page->doc->context, page_text);
return 1;
}
static int getPageSize(lua_State *L) {
fz_matrix ctm;
fz_rect bounds;
@ -323,7 +442,7 @@ static int getPageSize(lua_State *L) {
ctm = fz_concat(ctm, fz_rotate(dc->rotate));
bbox = fz_transform_rect(ctm, bounds);
lua_pushnumber(L, bbox.x1-bbox.x0);
lua_pushnumber(L, bbox.x1-bbox.x0);
lua_pushnumber(L, bbox.y1-bbox.y0);
return 2;
@ -423,6 +542,17 @@ static int drawPage(lua_State *L) {
return 0;
}
static int getCacheSize(lua_State *L) {
printf("## mupdf getCacheSize = %d\n", msize);
lua_pushnumber(L, msize);
return 1;
}
static int cleanCache(lua_State *L) {
printf("## mupdf cleanCache NOP\n");
return 0;
}
static const struct luaL_Reg pdf_func[] = {
{"openDocument", openDocument},
{NULL, NULL}
@ -433,8 +563,10 @@ static const struct luaL_Reg pdfdocument_meth[] = {
{"authenticatePassword", authenticatePassword},
{"openPage", openPage},
{"getPages", getNumberOfPages},
{"getTOC", getTableOfContent},
{"getToc", getTableOfContent},
{"close", closeDocument},
{"getCacheSize", getCacheSize},
{"cleanCache", cleanCache},
{"__gc", closeDocument},
{NULL, NULL}
};
@ -442,6 +574,7 @@ static const struct luaL_Reg pdfdocument_meth[] = {
static const struct luaL_Reg pdfpage_meth[] = {
{"getSize", getPageSize},
{"getUsedBBox", getUsedBBox},
{"getPageText", getPageText},
{"close", closePage},
{"__gc", closePage},
{"draw", drawPage},

@ -8,12 +8,12 @@ function PDFReader:open(filename)
-- muPDF manages its own cache, set second parameter
-- to the maximum size you want it to grow
local ok
ok, self.doc = pcall(pdf.openDocument, filename, 64*1024*1024)
ok, self.doc = pcall(pdf.openDocument, filename, self.cache_document_size)
if not ok then
return false, self.doc -- will contain error message
end
if self.doc:needsPassword() then
local password = InputBox:input(height-100, 100, "Pass:")
local password = InputBox:input(G_height-100, 100, "Pass:")
if not password or not self.doc:authenticatePassword(password) then
self.doc:close()
self.doc = nil
@ -30,3 +30,18 @@ function PDFReader:open(filename)
end
return true
end
----------------------------------------------------
-- highlight support
----------------------------------------------------
function PDFReader:getText(pageno)
local ok, page = pcall(self.doc.openPage, self.doc, pageno)
if not ok then
-- TODO: error handling
return nil
end
local text = page:getPageText()
--print("## page:getPageText "..dump(text)) -- performance impact on device
page:close()
return text
end

@ -20,11 +20,13 @@
require "alt_getopt"
require "pdfreader"
require "djvureader"
require "crereader"
require "filechooser"
require "settings"
require "screen"
require "keys"
require "commands"
require "dialog"
-- option parsing:
longopts = {
@ -42,17 +44,23 @@ function openFile(filename)
reader = DJVUReader
elseif file_type == "pdf" or file_type == "xps" or file_type == "cbz" then
reader = PDFReader
elseif file_type == "epub" or file_type == "txt" or file_type == "rtf" or file_type == "htm" or file_type == "html" or file_type == "fb2" or file_type == "chm" then
reader = CREReader
end
if reader then
InfoMessage:show("Opening document, please wait... ")
fb:refresh(0)
local ok, err = reader:open(filename)
if ok then
reader:loadSettings(filename)
page_num = reader.settings:readSetting("last_page") or 1
page_num = reader:getLastPageOrPos()
reader:goto(tonumber(page_num))
reader_settings:savesetting("lastfile", filename)
return reader:inputLoop()
else
-- TODO: error handling
InfoMessage:show("Error opening document.")
fb:refresh(0)
util.sleep(2)
end
end
return true -- on failed attempts, we signal to keep running
@ -66,9 +74,6 @@ function showusage()
print("-g, --goto=page start reading on page")
print("-G, --gamma=GAMMA set gamma correction")
print(" (floating point notation, e.g. \"1.5\")")
print("-d, --device=DEVICE set device specific configuration,")
print(" currently one of \"kdxg\" (default), \"k3\"")
print(" \"emu\" (DXG emulation)")
print("-h, --help show this usage help")
print("")
print("If you give the name of a directory instead of a file path, a file")
@ -86,18 +91,12 @@ if optarg["h"] then
return showusage()
end
if optarg["d"] == "k3" then
-- for now, the only difference is the additional input device
input.open("/dev/input/event0")
input.open("/dev/input/event1")
input.open("/dev/input/event2")
setK3Keycodes()
elseif optarg["d"] == "emu" then
if util.isEmulated()==1 then
input.open("")
-- SDL key codes
setEmuKeycodes()
else
input.open("slider")
input.open("/dev/input/event0")
input.open("/dev/input/event1")
@ -116,16 +115,16 @@ if optarg["G"] ~= nil then
end
fb = einkfb.open("/dev/fb0")
width, height = fb:getSize()
G_width, G_height = fb:getSize()
-- read current rotation mode
Screen:updateRotationMode()
origin_rotation_mode = Screen.cur_rotation_mode
Screen.native_rotation_mode = Screen.cur_rotation_mode
-- set up reader's setting: font
reader_settings = DocSettings:open(".reader")
r_cfont = reader_settings:readSetting("cfont")
if r_cfont ~=nil then
Font.cfont = r_cfont
fontmap = reader_settings:readSetting("fontmap")
if fontmap ~= nil then
Font.fontmap = fontmap
end
-- initialize global settings shared among all readers
@ -133,6 +132,7 @@ UniReader:initGlobalSettings(reader_settings)
-- initialize specific readers
PDFReader:init()
DJVUReader:init()
CREReader:init()
-- display directory or open file
local patharg = reader_settings:readSetting("lastfile")
@ -140,12 +140,13 @@ if ARGV[optind] and lfs.attributes(ARGV[optind], "mode") == "directory" then
local running = true
FileChooser:setPath(ARGV[optind])
while running do
local file, callback = FileChooser:choose(0,height)
local file, callback = FileChooser:choose(0, G_height)
if callback then
callback()
else
if file ~= nil then
running = openFile(file)
print(file)
else
running = false
end
@ -161,15 +162,15 @@ end
-- save reader settings
reader_settings:savesetting("cfont", Font.cfont)
reader_settings:savesetting("fontmap", Font.fontmap)
reader_settings:close()
-- @TODO dirty workaround, find a way to force native system poll
-- screen orientation and upside down mode 09.03 2012
fb:setOrientation(origin_rotation_mode)
fb:setOrientation(Screen.native_rotation_mode)
input.closeAll()
--os.execute('test -e /proc/keypad && echo "send '..KEY_HOME..'" > /proc/keypad ')
if optarg["d"] ~= "emu" then
if util.isEmulated()==0 then
--os.execute("killall -cont cvm")
os.execute('echo "send '..KEY_MENU..'" > /proc/keypad;echo "send '..KEY_MENU..'" > /proc/keypad')
end

@ -13,6 +13,7 @@ function glyphCacheClaim(size)
glyphcache[k].age = glyphcache[k].age - 1
else
glyphcache_current_memsize = glyphcache_current_memsize - glyphcache[k].size
glyphcache[k].glyph.bb:free()
glyphcache[k] = nil
end
end
@ -20,10 +21,10 @@ function glyphCacheClaim(size)
glyphcache_current_memsize = glyphcache_current_memsize + size
return true
end
function getGlyph(face, facehash, charcode)
local hash = glyphCacheHash(facehash, charcode)
function getGlyph(face, charcode)
local hash = glyphCacheHash(face.hash, charcode)
if glyphcache[hash] == nil then
local glyph = face:renderGlyph(charcode)
local glyph = face.ftface:renderGlyph(charcode)
local size = glyph.bb:getWidth() * glyph.bb:getHeight() / 2 + 32
glyphCacheClaim(size);
glyphcache[hash] = {
@ -88,9 +89,9 @@ function renderUtf8Text(buffer, x, y, face, facehash, text, kerning, backgroundC
for uchar in string.gfind(text, "([%z\1-\127\194-\244][\128-\191]*)") do
if pen_x < buffer:getWidth() then
local charcode = util.utf8charcode(uchar)
local glyph = getGlyph(face, facehash, charcode)
local glyph = getGlyph(face, charcode)
if kerning and prevcharcode then
local kern = face:getKerning(prevcharcode, charcode)
local kern = face.ftface:getKerning(prevcharcode, charcode)
pen_x = pen_x + kern
--print("prev:"..string.char(prevcharcode).." curr:"..string.char(charcode).." pen_x:"..pen_x.." kern:"..kern)
buffer:addblitFrom(glyph.bb, x + pen_x + glyph.l, y - glyph.t, 0, 0, glyph.bb:getWidth(), glyph.bb:getHeight())

@ -1,36 +0,0 @@
#!./kpdfview
require "rendertext"
require "graphics"
fb = einkfb.open("/dev/fb0")
width, height = fb:getSize()
print("open")
size = 50
--face = freetype.newBuiltinFace("sans", 64)
face = freetype.newFace("/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf", size)
print("got face")
if face:hasKerning() then
print("has kerning")
end
width, height = fb:getSize()
fb.bb:paintRect(5,5,width-5,height-5,4);
faceHeight, faceAscender = face:getHeightAndAscender();
print("face height:"..tostring(faceHeight).." - ascender:"..faceAscender)
faceHeight = math.ceil(faceHeight)
faceAscender = math.ceil(faceAscender)
print("face height:"..tostring(faceHeight).." - ascender:"..faceAscender)
posY = 5 + faceAscender
renderUtf8Text(fb.bb, 5, posY, face, "h", "AV T.T: gxyt!", true)
posY = posY + faceHeight
renderUtf8Text(fb.bb, 5, posY, face, "h2", "AV T.T: gxyt!", false)
fb:refresh()
while true do
local ev = input.waitForEvent()
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@ -41,6 +41,11 @@ Codes for rotation modes:
Screen = {
cur_rotation_mode = 0,
-- these two variabls are used to help switching from framework to reader
native_rotation_mode = nil,
kpv_rotation_mode = nil,
saved_bb = nil,
}
-- @orien: 1 for clockwise rotate, -1 for anti-clockwise
@ -71,4 +76,29 @@ function Screen:updateRotationMode()
end
end
function Screen:saveCurrentBB()
local width, height = G_width, G_height
if not self.saved_bb then
self.saved_bb = Blitbuffer.new(width, height)
end
if self.saved_bb:getWidth() ~= width then
self.saved_bb:free()
self.saved_bb = Blitbuffer.new(width, height)
end
self.saved_bb:blitFullFrom(fb.bb)
end
function Screen:restoreFromSavedBB()
self:restoreFromBB(self.saved_bb)
end
function Screen:getCurrentScreenBB()
local bb = Blitbuffer.new(G_width, G_height)
bb:blitFullFrom(fb.bb)
return bb
end
function Screen:restoreFromBB(bb)
fb.bb:blitFullFrom(bb)
end

@ -2,6 +2,7 @@ require "rendertext"
require "keys"
require "graphics"
require "font"
require "commands"
SelectMenu = {
-- font for displaying item names
@ -11,8 +12,7 @@ SelectMenu = {
-- font for paging display
ffsize = 16,
-- font for item shortcut
sface = freetype.newBuiltinFace("mono", 22),
sfhash = "mono22",
sface = Font:getFace("scfont", 22),
-- title height
title_H = 40,
@ -21,7 +21,7 @@ SelectMenu = {
-- foot height
foot_H = 27,
menu_title = "None Titled",
menu_title = "No Title",
no_item_msg = "No items found.",
item_array = {},
items = 0,
@ -32,10 +32,14 @@ SelectMenu = {
"Z", "X", "C", "V", "B", "N", "M", ".", "Sym", "Ent",
},
last_shortcut = 0,
-- state buffer
page = 1,
current = 1,
oldcurrent = 0,
selected_item = nil,
commands = nil,
}
function SelectMenu:new(o)
@ -46,10 +50,12 @@ function SelectMenu:new(o)
o.page = 1
o.current = 1
o.oldcurrent = 0
o.selected_item = nil
-- increase spacing for DXG so we don't have more than 30 shortcuts
if fb.bb:getHeight() == 1200 then
o.spacing = 37
end
o:addAllCommands()
return o
end
@ -62,74 +68,197 @@ function SelectMenu:getItemIndexByShortCut(c, perpage)
end
end
--[
-- return the index of selected item
--]
function SelectMenu:choose(ypos, height)
local perpage = math.floor(height / self.spacing) - 2
local pagedirty = true
local markerdirty = false
local prevItem = function ()
if self.current == 1 then
if self.page > 1 then
self.current = perpage
self.page = self.page - 1
pagedirty = true
function SelectMenu:addAllCommands()
self.commands = Commands:new{}
self.commands:add(KEY_FW_UP, nil, "",
"previous item",
function(sm)
if sm.current == 1 then
if sm.page > 1 then
sm.current = sm.perpage
sm.page = sm.page - 1
sm.pagedirty = true
end
else
sm.current = sm.current - 1
sm.markerdirty = true
end
else
self.current = self.current - 1
markerdirty = true
end
end
local nextItem = function ()
if self.current == perpage then
if self.page < (self.items / perpage) then
self.current = 1
self.page = self.page + 1
pagedirty = true
)
self.commands:add(KEY_FW_DOWN, nil, "",
"next item",
function(sm)
if sm.current == sm.perpage then
if sm.page < (sm.items / sm.perpage) then
sm.current = 1
sm.page = sm.page + 1
sm.pagedirty = true
end
else
if sm.page ~= math.floor(sm.items / sm.perpage) + 1
or sm.current + (sm.page - 1) * sm.perpage < sm.items then
sm.current = sm.current + 1
sm.markerdirty = true
end
end
else
if self.page ~= math.floor(self.items / perpage) + 1
or self.current + (self.page-1)*perpage < self.items then
self.current = self.current + 1
markerdirty = true
end
)
self.commands:add({KEY_PGFWD, KEY_LPGFWD}, nil, "",
"next page",
function(sm)
if sm.page < (sm.items / sm.perpage) then
if sm.current + sm.page * sm.perpage > sm.items then
sm.current = sm.items - sm.page * sm.perpage
end
sm.page = sm.page + 1
sm.pagedirty = true
else
sm.current = sm.items - (sm.page - 1) * sm.perpage
sm.markerdirty = true
end
end
)
self.commands:add({KEY_PGBCK, KEY_LPGBCK}, nil, "",
"previous page",
function(sm)
if sm.page > 1 then
sm.page = sm.page - 1
sm.pagedirty = true
else
sm.current = 1
sm.markerdirty = true
end
end
)
self.commands:add(KEY_FW_PRESS, nil, "",
"select menu item",
function(sm)
if sm.last_shortcut < 30 then
if sm.items == 0 then
return "break"
else
self.selected_item = (sm.perpage * (sm.page - 1)
+ sm.current)
end
end
end
)
local KEY_Q_to_P = {}
for i = KEY_Q, KEY_P do
table.insert(KEY_Q_to_P, Keydef:new(i, nil, ""))
end
self.commands:addGroup("Q to P", KEY_Q_to_P,
"Select menu item with Q to E key as shortcut",
function(sm, keydef)
sm.selected_item = sm:getItemIndexByShortCut(
sm.item_shortcuts[ keydef.keycode - KEY_Q + 1 ], sm.perpage)
end
)
local KEY_A_to_L = {}
for i = KEY_A, KEY_L do
table.insert(KEY_A_to_L, Keydef:new(i, nil, ""))
end
self.commands:addGroup("A to L", KEY_A_to_L,
"Select menu item with A to L key as shortcut",
function(sm, keydef)
sm.selected_item = sm:getItemIndexByShortCut(
sm.item_shortcuts[ keydef.keycode - KEY_A + 11 ], sm.perpage)
end
)
local KEY_Z_to_M = {}
for i = KEY_Z, KEY_M do
table.insert(KEY_Z_to_M, Keydef:new(i, nil, ""))
end
self.commands:addGroup("Z to M", KEY_Z_to_M,
"Select menu item with Z to M key as shortcut",
function(sm, keydef)
sm.selected_item = sm:getItemIndexByShortCut(
sm.item_shortcuts[ keydef.keycode - KEY_Z + 21 ], sm.perpage)
end
)
self.commands:add(KEY_DEL, nil, "",
"Select menu item with del key as shortcut",
function(sm)
sm.selected_item = sm:getItemIndexByShortCut("Del", sm.perpage)
end
)
self.commands:add(KEY_DOT, nil, "",
"Select menu item with dot key as shortcut",
function(sm)
sm.selected_item = sm:getItemIndexByShortCut(".", sm.perpage)
end
)
self.commands:add({KEY_SYM, KEY_SLASH}, nil, "",
"Select menu item with sym/slash key as shortcut",
function(sm)
-- DXG has slash after dot
sm.selected_item = sm:getItemIndexByShortCut("Sym", sm.perpage)
end
)
self.commands:add(KEY_ENTER, nil, "",
"Select menu item with enter key as shortcut",
function(sm)
sm.selected_item = sm:getItemIndexByShortCut("Ent", sm.perpage)
end
)
self.commands:add(KEY_BACK, nil, "",
"Exit menu",
function(sm)
return "break"
end
)
end
function SelectMenu:clearCommands()
self.commands = Commands:new{}
self.commands:add(KEY_BACK, nil, "",
"Exit menu",
function(sm)
return "break"
end)
end
------------------------------------------------
-- return the index of selected item
------------------------------------------------
function SelectMenu:choose(ypos, height)
self.perpage = math.floor(height / self.spacing) - 2
self.pagedirty = true
self.markerdirty = false
self.last_shortcut = 0
while true do
local cface, cfhash = Font:getFaceAndHash(22)
local tface, tfhash = Font:getFaceAndHash(25, Font.tfont)
local fface, ffhash = Font:getFaceAndHash(16, Font.ffont)
local cface = Font:getFace("cfont", 22)
local tface = Font:getFace("tfont", 25)
local fface = Font:getFace("ffont", 16)
if pagedirty then
markerdirty = true
if self.pagedirty then
self.markerdirty = true
-- draw menu title
fb.bb:paintRect(0, ypos, fb.bb:getWidth(), self.title_H + 10, 0)
fb.bb:paintRect(10, ypos + 10, fb.bb:getWidth() - 20, self.title_H, 5)
local x = 20
local y = ypos + self.title_H
renderUtf8Text(fb.bb, x, y, tface, tfhash, self.menu_title, true)
renderUtf8Text(fb.bb, x, y, tface, self.menu_title, true)
-- draw items
fb.bb:paintRect(0, ypos + self.title_H + 10, fb.bb:getWidth(), height - self.title_H, 0)
if self.items == 0 then
y = ypos + self.title_H + (self.spacing * 2)
renderUtf8Text(fb.bb, 30, y, cface, cfhash,
renderUtf8Text(fb.bb, 30, y, cface,
"Oops... Bad news for you:", true)
y = y + self.spacing
renderUtf8Text(fb.bb, 30, y, cface, cfhash,
renderUtf8Text(fb.bb, 30, y, cface,
self.no_item_msg, true)
markerdirty = false
self.markerdirty = false
self:clearCommands()
else
local c
for c = 1, perpage do
local i = (self.page - 1) * perpage + c
for c = 1, self.perpage do
local i = (self.page - 1) * self.perpage + c
if i <= self.items then
y = ypos + self.title_H + (self.spacing * c)
@ -142,30 +271,32 @@ function SelectMenu:choose(ypos, height)
if self.item_shortcuts[c] ~= nil and
string.len(self.item_shortcuts[c]) == 3 then
-- print "Del", "Sym and "Ent"
renderUtf8Text(fb.bb, 13, y, fface, ffhash,
renderUtf8Text(fb.bb, 13, y, fface,
self.item_shortcuts[c], true)
else
renderUtf8Text(fb.bb, 18, y, self.sface, self.sfhash,
renderUtf8Text(fb.bb, 18, y, self.sface,
self.item_shortcuts[c], true)
end
self.last_shortcut = c
renderUtf8Text(fb.bb, 50, y, cface, cfhash,
renderUtf8Text(fb.bb, 50, y, cface,
self.item_array[i], true)
end
end
end
end -- EOF if i <= self.items
end -- EOF for
end -- EOF if
-- draw footer
y = ypos + self.title_H + (self.spacing * perpage) + self.foot_H + 5
y = ypos + self.title_H + (self.spacing * self.perpage)
+ self.foot_H + 5
x = (fb.bb:getWidth() / 2) - 50
renderUtf8Text(fb.bb, x, y, fface, ffhash,
"Page "..self.page.." of "..(math.floor(self.items / perpage)+1), true)
renderUtf8Text(fb.bb, x, y, fface,
"Page "..self.page.." of "..
(math.ceil(self.items / self.perpage)), true)
end
if markerdirty then
if not pagedirty then
if self.markerdirty then
if not self.pagedirty then
if self.oldcurrent > 0 then
y = ypos + self.title_H + (self.spacing * self.oldcurrent) + 8
fb.bb:paintRect(45, y, fb.bb:getWidth() - 60, 3, 0)
@ -175,72 +306,41 @@ function SelectMenu:choose(ypos, height)
-- draw new marker line
y = ypos + self.title_H + (self.spacing * self.current) + 8
fb.bb:paintRect(45, y, fb.bb:getWidth() - 60, 3, 15)
if not pagedirty then
if not self.pagedirty then
fb:refresh(1, 45, y, fb.bb:getWidth() - 60, 3)
end
self.oldcurrent = self.current
markerdirty = false
self.markerdirty = false
end
if pagedirty then
if self.pagedirty then
fb:refresh(0, 0, ypos, fb.bb:getWidth(), height)
pagedirty = false
self.pagedirty = false
end
local ev = input.waitForEvent()
local ev = input.saveWaitForEvent()
ev.code = adjustKeyEvents(ev)
if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then
local selected = nil
if ev.code == KEY_FW_UP then
prevItem()
elseif ev.code == KEY_FW_DOWN then
nextItem()
elseif ev.code == KEY_PGFWD or ev.code == KEY_LPGFWD then
if self.page < (self.items / perpage) then
if self.current + self.page*perpage > self.items then
self.current = self.items - self.page*perpage
end
self.page = self.page + 1
pagedirty = true
else
self.current = self.items - (self.page-1)*perpage
markerdirty = true
end
elseif ev.code == KEY_PGBCK or ev.code == KEY_LPGBCK then
if self.page > 1 then
self.page = self.page - 1
pagedirty = true
else
self.current = 1
markerdirty = true
end
elseif ev.code == KEY_FW_PRESS or ev.code == KEY_ENTER and self.last_shortcut < 30 then
if self.items == 0 then
return nil
else
return (perpage*(self.page-1) + self.current)
end
elseif ev.code >= KEY_Q and ev.code <= KEY_P then
selected = self:getItemIndexByShortCut(self.item_shortcuts[ ev.code - KEY_Q + 1 ], perpage)
elseif ev.code >= KEY_A and ev.code <= KEY_L then
selected = self:getItemIndexByShortCut(self.item_shortcuts[ ev.code - KEY_A + 11], perpage)
elseif ev.code >= KEY_Z and ev.code <= KEY_M then
selected = self:getItemIndexByShortCut(self.item_shortcuts[ ev.code - KEY_Z + 21], perpage)
elseif ev.code == KEY_DEL then
selected = self:getItemIndexByShortCut("Del", perpage)
elseif ev.code == KEY_DOT then
selected = self:getItemIndexByShortCut(".", perpage)
elseif ev.code == KEY_SYM or ev.code == KEY_SLASH then -- DXG has slash after dot
selected = self:getItemIndexByShortCut("Sym", perpage)
elseif ev.code == KEY_ENTER then
selected = self:getItemIndexByShortCut("Ent", perpage)
elseif ev.code == KEY_BACK then
return nil
keydef = Keydef:new(ev.code, getKeyModifier())
print("key pressed: "..tostring(keydef))
command = self.commands:getByKeydef(keydef)
if command ~= nil then
print("command to execute: "..tostring(command))
ret_code = command.func(self, keydef)
else
print("command not found: "..tostring(command))
end
if selected ~= nil then
print("# selected "..selected)
return selected
if ret_code == "break" then
break
end
end
end
if self.selected_item ~= nil then
print("# selected "..self.selected_item)
return self.selected_item, self.item_array[self.selected_item]
end
end -- EOF if
end -- EOF while
return nil
end

@ -0,0 +1,87 @@
/*
KindlePDFViewer: power slider key event watcher
Copyright (C) 2012 Qingping Hou <dave2008713@gamil.com>
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 <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/input.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#define OUTPUT_SIZE 21
#define EVENT_PIPE "/tmp/event_slider"
#define CODE_IN_SAVER 10000
#define CODE_OUT_SAVER 10001
int
main ( int argc, char *argv[] )
{
int fd, ret;
FILE *fp;
char std_out[OUTPUT_SIZE] = "";
struct input_event ev;
__u16 key_code = 10000;
/* create the npipe if not exists */
/*if(access(argv[1], F_OK)){*/
/*printf("npipe %s not found, try to create it...\n", argv[1]);*/
/*if(mkfifo(argv[1], 0777)) {*/
/*printf("Create npipe %s failed!\n", argv[1]);*/
/*}*/
/*}*/
/* open npipe for writing */
fd = open(argv[1], O_RDWR | O_NONBLOCK);
if(fd < 0) {
printf("Open %s falied: %s\n", argv[1], strerror(errno));
exit(EXIT_FAILURE);
}
/* initialize event struct */
ev.type = EV_KEY;
ev.code = key_code;
ev.value = 1;
while(1) {
/* listen power slider events */
memset(std_out, 0, OUTPUT_SIZE);
fp = popen("lipc-wait-event -s 0 com.lab126.powerd goingToScreenSaver,outOfScreenSaver", "r");
ret = fread(std_out, OUTPUT_SIZE, 1, fp);
pclose(fp);
/* fill event struct */
gettimeofday(&ev.time, NULL);
if(std_out[0] == 'g') {
ev.code = CODE_IN_SAVER;
} else if(std_out[0] == 'o') {
ev.code = CODE_OUT_SAVER;
} else {
printf("Unrecognized event.\n");
exit(EXIT_FAILURE);
}
/* generate event */
ret = write(fd, &ev, sizeof(struct input_event));
}
close(fd);
return EXIT_SUCCESS;
}

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

@ -17,6 +17,7 @@
*/
#include <sys/time.h>
#include <unistd.h>
#include "util.h"
@ -28,6 +29,19 @@ static int gettime(lua_State *L) {
return 2;
}
static int util_sleep(lua_State *L) {
unsigned int seconds = luaL_optint(L, 1, 0);
sleep(seconds);
return 0;
}
static int util_usleep(lua_State *L) {
useconds_t useconds = luaL_optint(L, 1, 0);
usleep(useconds);
return 0;
}
/* Turn UTF-8 char code to Unicode */
static int utf8charcode(lua_State *L) {
size_t len;
const char* utf8char = luaL_checklstring(L, 1, &len);
@ -46,9 +60,21 @@ static int utf8charcode(lua_State *L) {
return 1;
}
static int isEmulated(lua_State *L) {
#ifdef EMULATE_READER
lua_pushinteger(L, 1);
#else
lua_pushinteger(L, 0);
#endif
return 1;
}
static const struct luaL_Reg util_func[] = {
{"gettime", gettime},
{"sleep", util_sleep},
{"usleep", util_usleep},
{"utf8charcode", utf8charcode},
{"isEmulated", isEmulated},
{NULL, NULL}
};

@ -0,0 +1,263 @@
require "rendertext"
require "graphics"
require "image"
--[[
This is a (useless) generic Widget interface
widgets can be queried about their size and can be paint.
that's it for now. Probably we need something more elaborate
later.
]]
Widget = {
dimen = { w = 0, h = 0},
}
function Widget:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Widget:getSize()
return self.dimen
end
function Widget:paintTo(bb, x, y)
end
function Widget:free()
end
--[[
WidgetContainer is a container for another Widget
]]
WidgetContainer = Widget:new()
function WidgetContainer:free()
for _, widget in ipairs(self) do
widget:free()
end
end
--[[
CenterContainer centers its content (1 widget) within its own dimensions
]]
CenterContainer = WidgetContainer:new()
function CenterContainer:paintTo(bb, x, y)
local contentSize = self[1]:getSize()
if contentSize.w > self.dimen.w or contentSize.h > self.dimen.h then
-- throw error?
return
end
self[1]:paintTo(bb,
x + (self.dimen.w - contentSize.w)/2,
y + (self.dimen.h - contentSize.h)/2)
end
--[[
A FrameContainer is some graphics content (1 widget) that is surrounded by a frame
]]
FrameContainer = WidgetContainer:new({
background = nil,
color = 15,
margin = 0,
bordersize = 2,
padding = 5,
})
function FrameContainer:getSize()
local content_size = self[1]:getSize()
return {
w = content_size.w + ( self.margin + self.bordersize + self.padding ) * 2,
h = content_size.h + ( self.margin + self.bordersize + self.padding ) * 2
}
end
function FrameContainer:paintTo(bb, x, y)
local my_size = self:getSize()
if self.background then
bb:paintRect(x, y, my_size.w, my_size.h, self.background)
end
if self.bordersize > 0 then
bb:paintBorder(x + self.margin, y + self.margin,
my_size.w - self.margin * 2, my_size.h - self.margin * 2,
self.bordersize, self.color)
end
self[1]:paintTo(bb,
x + self.margin + self.bordersize + self.padding,
y + self.margin + self.bordersize + self.padding)
end
--[[
A TextWidget puts a string on a single line
]]
TextWidget = Widget:new({
text = nil,
face = nil,
color = 15,
_bb = nil,
_length = 0,
_maxlength = 1200,
})
function TextWidget:_render()
local h = self.face.size * 1.5
self._bb = Blitbuffer.new(self._maxlength, h)
self._length = renderUtf8Text(self._bb, 0, h*.7, self.face, self.text, self.color)
end
function TextWidget:getSize()
if not self._bb then
self:_render()
end
return { w = self._length, h = self._bb:getHeight() }
end
function TextWidget:paintTo(bb, x, y)
if not self._bb then
self:_render()
end
bb:blitFrom(self._bb, x, y, 0, 0, self._length, self._bb:getHeight())
end
function TextWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
--[[
ImageWidget shows an image from a file
]]
ImageWidget = Widget:new({
file = nil,
_bb = nil
})
function ImageWidget:_render()
local itype = string.lower(string.match(self.file, ".+%.([^.]+)") or "")
if itype == "jpeg" or itype == "jpg" then
self._bb = Image.fromJPEG(self.file)
elseif itype == "png" then
self._bb = Image.fromPNG(self.file)
end
end
function ImageWidget:getSize()
if not self._bb then
self:_render()
end
return { w = self._bb:getWidth(), h = self._bb:getHeight() }
end
function ImageWidget:paintTo(bb, x, y)
local size = self:getSize()
bb:blitFrom(self._bb, x, y, 0, 0, size.w, size.h)
end
function ImageWidget:free()
if self._bb then
self._bb:free()
self._bb = nil
end
end
--[[
A Layout widget that puts objects besides each others
]]
HorizontalGroup = WidgetContainer:new({
align = "center",
_size = nil,
})
function HorizontalGroup:getSize()
if not self._size then
self._size = { w = 0, h = 0 }
self._offsets = { }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
self._offsets[i] = {
x = self._size.w,
y = w_size.h
}
self._size.w = self._size.w + w_size.w
if w_size.h > self._size.h then
self._size.h = w_size.h
end
end
end
return self._size
end
function HorizontalGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, widget in ipairs(self) do
if self.align == "center" then
widget:paintTo(bb, x + self._offsets[i].x, y + (size.h - self._offsets[i].y) / 2)
elseif self.align == "top" then
widget:paintTo(bb, x + self._offsets[i].x, y)
elseif self.align == "bottom" then
widget:paintTo(bb, x + self._offsets[i].x, y + size.h - self._offsets[i].y)
end
end
end
function HorizontalGroup:free()
self._size = nil
self._offsets = {}
WidgetContainer.free(self)
end
--[[
A Layout widget that puts objects under each other
]]
VerticalGroup = WidgetContainer:new({
align = "center",
_size = nil,
_offsets = {}
})
function VerticalGroup:getSize()
if not self._size then
self._size = { w = 0, h = 0 }
self._offsets = { }
for i, widget in ipairs(self) do
local w_size = widget:getSize()
self._offsets[i] = {
x = w_size.w,
y = self._size.h,
}
self._size.h = self._size.h + w_size.h
if w_size.w > self._size.w then
self._size.w = w_size.w
end
end
end
return self._size
end
function VerticalGroup:paintTo(bb, x, y)
local size = self:getSize()
for i, widget in ipairs(self) do
if self.align == "center" then
widget:paintTo(bb, x + (size.w - self._offsets[i].x) / 2, y + self._offsets[i].y)
elseif self.align == "left" then
widget:paintTo(bb, x, y + self._offsets[i].y)
elseif self.align == "right" then
widget:paintTo(bb, x + size.w - self._offsets[i].x, y + self._offsets[i].y)
end
end
end
function VerticalGroup:free()
self._size = nil
self._offsets = {}
WidgetContainer.free(self)
end
Loading…
Cancel
Save