From a231b944c13779c0fdbde3759f1b081c0154cafc Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 15 Mar 2012 17:03:09 +0800 Subject: [PATCH 001/183] add: first demo of cursor support --- blitbuffer.c | 58 ++++++++++++++++++++++++++++++++++++++++++ graphics.lua | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ inputbox.lua | 71 +++++++++++++++++++++++++++------------------------- 3 files changed, 162 insertions(+), 34 deletions(-) diff --git a/blitbuffer.c b/blitbuffer.c index c8a51de7f..8ea96a7a6 100644 --- a/blitbuffer.c +++ b/blitbuffer.c @@ -366,6 +366,63 @@ 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 const struct luaL_reg blitbuffer_func[] = { {"new", newBlitBuffer}, {NULL, NULL} @@ -378,6 +435,7 @@ static const struct luaL_reg blitbuffer_meth[] = { {"addblitFrom", addblitToBuffer}, {"blitFullFrom", blitFullToBuffer}, {"paintRect", paintRect}, + {"invertRect", invertRect}, {"free", freeBlitBuffer}, {"__gc", freeBlitBuffer}, {NULL, NULL} diff --git a/graphics.lua b/graphics.lua index 1ebb13cce..bebefc419 100644 --- a/graphics.lua +++ b/graphics.lua @@ -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,69 @@ 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, +} + +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.h = o.h or self.h + + o.w = o.h / 2 + o.line_w = math.floor(o.h / 10) + + setmetatable(o, self) + self.__index = self + return o +end + +function Cursor:_draw(x, y) + local body_h = self.h - self.line_w + blitbuffer.invertRect(fb.bb, x, y, self.w, self.line_w) + --print("self.w: "..self.w..", self.line_w: "..self.line_w) + blitbuffer.invertRect(fb.bb, x+(self.w/2)-(self.line_w/2), y+self.line_w, + self.line_w, body_h-self.line_w) + blitbuffer.invertRect(fb.bb, x, y+body_h, self.w, self.line_w) +end + +function Cursor:draw() + self:_draw(self.x_pos, self.y_pos) +end + +function Cursor:clear() + self:_draw(self.x_pos, self.y_pos) +end + +function Cursor:move(x_off, y_off) + self:clear() + self.x_pos = self.x_pos + x_off + self.y_pos = self.y_pos + y_off + self:draw() +end + +function Cursor:moveHorizontal(x_off) + self:clear() + self.x_pos = self.x_pos + x_off + self:draw() +end + +function Cursor:moveVertical(y_off) + self:clear() + self.y_pos = self.y_pos + y_off + self:draw() +end + diff --git a/inputbox.lua b/inputbox.lua index 6e3fc6667..e845ccd79 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -4,6 +4,7 @@ require "graphics" InputBox = { -- Class vars: + h = 100, input_start_x = 145, input_start_y = nil, input_cur_x = nil, -- points to the start of next input pos @@ -15,6 +16,8 @@ InputBox = { shiftmode = false, altmode = false, + cursor = nil, + -- font for displaying input content face = freetype.newBuiltinFace("mono", 25), fhash = "m25", @@ -22,13 +25,6 @@ InputBox = { fwidth = 16, } -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 -end - function InputBox:addString(str) for i = 1, #str do self:addChar(str:sub(i,i)) @@ -36,9 +32,13 @@ function InputBox:addString(str) 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:moveHorizontal(self.fwidth) + 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.cursor.w - self.fwidth, + self.input_start_y-25, + self.fwidth*2 + self.cursor.w*2, self.h-25) self.input_cur_x = self.input_cur_x + self.fwidth self.input_string = self.input_string .. char end @@ -50,8 +50,10 @@ function InputBox:delChar() 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.fwidth, self.fheight, self.input_bg) + self.cursor:moveHorizontal(-self.fwidth) + fb:refresh(1, self.input_cur_x, self.input_start_y-25, + self.fwidth + self.cursor.w, self.h-25) self.input_string = self.input_string:sub(0,-2) end @@ -66,35 +68,36 @@ function InputBox:drawBox(ypos, w, h, title) 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 -- do some initilization + self.h = height self.input_start_y = ypos + 35 self.input_cur_x = self.input_start_x - - 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 = "" + self.cursor = Cursor:new { + x_pos = 140, + y_pos = ypos + 13, + h = 30, + } + + if d_text then + self.input_string = d_text end - 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 + -- draw box and content + w = fb.bb:getWidth() - 40 + h = height - 45 + self:drawBox(ypos, w, h, title) + self.cursor:draw() + self:addString(self.input_string) + fb:refresh(1, 20, ypos, w, h) + while true do local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then From 7b0f2ad815e08b6d1982082c8011276aeb35523b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 15 Mar 2012 21:43:55 +0800 Subject: [PATCH 002/183] mod: cursor finished, add to inputbox --- graphics.lua | 36 ++++++++++++------ inputbox.lua | 105 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 105 insertions(+), 36 deletions(-) diff --git a/graphics.lua b/graphics.lua index bebefc419..f12510252 100644 --- a/graphics.lua +++ b/graphics.lua @@ -50,7 +50,7 @@ function Cursor:new(o) o.y_pos = o.y_pos or self.y_pos o.h = o.h or self.h - o.w = o.h / 2 + o.w = o.h / 3 o.line_w = math.floor(o.h / 10) setmetatable(o, self) @@ -60,11 +60,13 @@ end function Cursor:_draw(x, y) local body_h = self.h - self.line_w - blitbuffer.invertRect(fb.bb, x, y, self.w, self.line_w) - --print("self.w: "..self.w..", self.line_w: "..self.line_w) - blitbuffer.invertRect(fb.bb, x+(self.w/2)-(self.line_w/2), y+self.line_w, - self.line_w, body_h-self.line_w) - blitbuffer.invertRect(fb.bb, x, y+body_h, self.w, self.line_w) + -- paint upper horizontal line + fb.bb:invertRect(x, y, self.w, self.line_w/2) + -- paint middle vertical line + fb.bb:invertRect(x+(self.w/2)-(self.line_w/2), y+self.line_w/2, + self.line_w, body_h) + -- paint lower horizontal line + fb.bb:invertRect(x, y+body_h+self.line_w/2, self.w, self.line_w/2) end function Cursor:draw() @@ -76,21 +78,33 @@ function Cursor:clear() end function Cursor:move(x_off, y_off) - self:clear() self.x_pos = self.x_pos + x_off self.y_pos = self.y_pos + y_off - self:draw() end function Cursor:moveHorizontal(x_off) - self:clear() 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:moveVertical(y_off) +function Cursor:moveHorizontalAndDraw(x_off) self:clear() - self.y_pos = self.y_pos + y_off + self:move(x_off, 0) + self:draw() +end + +function Cursor:moveVerticalAndDraw(y_off) + self:clear() + self:move(0, y_off) self:draw() end diff --git a/inputbox.lua b/inputbox.lua index e845ccd79..0f1e3c0f2 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -5,6 +5,7 @@ require "graphics" 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 @@ -19,10 +20,11 @@ InputBox = { cursor = nil, -- font for displaying input content + -- we have to use mono here for better distance controlling face = freetype.newBuiltinFace("mono", 25), fhash = "m25", fheight = 25, - fwidth = 16, + fwidth = 15, } function InputBox:addString(str) @@ -31,30 +33,65 @@ function InputBox:addString(str) 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.fhash, + self.input_string, 0) +end + function InputBox:addChar(char) - self.cursor:moveHorizontal(self.fwidth) - 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.cursor.w - self.fwidth, - self.input_start_y-25, - self.fwidth*2 + self.cursor.w*2, self.h-25) + 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(0,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 + + 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(0,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) + -- draw new cursor self.cursor:moveHorizontal(-self.fwidth) - fb:refresh(1, self.input_cur_x, self.input_start_y-25, - self.fwidth + self.cursor.w, self.h-25) - self.input_string = self.input_string:sub(0,-2) + 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:drawBox(ypos, w, h, title) @@ -79,29 +116,31 @@ function InputBox:input(ypos, height, title, d_text) 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 = 140, + x_pos = self.input_start_x - 3, y_pos = ypos + 13, h = 30, } - if d_text then - self.input_string = d_text - end -- draw box and content w = fb.bb:getWidth() - 40 h = height - 45 self:drawBox(ypos, w, h, title) self.cursor:draw() - self:addString(self.input_string) + if d_text then + self.input_string = d_text + self:addString(self.input_string) + end fb:refresh(1, 20, ypos, w, h) while true do local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - --local secs, usecs = util.gettime() + 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 @@ -180,21 +219,37 @@ function InputBox:input(ypos, height, title, d_text) self:addChar(" ") elseif ev.code == KEY_PGFWD then elseif ev.code == KEY_PGBCK then + elseif ev.code == KEY_FW_LEFT then + if (self.cursor.x_pos + 3) > self.input_start_x then + self.cursor:moveHorizontalAndDraw(-self.fwidth) + fb:refresh(1, self.input_start_x-5, ypos, + self.input_slot_w, h) + end + elseif ev.code == KEY_FW_RIGHT then + if (self.cursor.x_pos + 3) < self.input_cur_x then + self.cursor:moveHorizontalAndDraw(self.fwidth) + fb:refresh(1,self.input_start_x-5, ypos, + self.input_slot_w, h) + end 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() + if Keys.shiftmode then + self:clearText() + else + self:delChar() + end elseif ev.code == KEY_BACK then self.input_string = nil break 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) + local nsecs, nusecs = util.gettime() + local dur = (nsecs - secs) * 1000000 + nusecs - usecs + print("E: T="..ev.type.." V="..ev.value.." C="..ev.code.." DUR="..dur) end -- if end -- while From 709a9003d276d74e28417b28464db6cd2a0f19d1 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 15 Mar 2012 22:15:09 +0800 Subject: [PATCH 003/183] fix: handle default text in inputbox --- inputbox.lua | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/inputbox.lua b/inputbox.lua index 0f1e3c0f2..a7cdc8eca 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -27,12 +27,6 @@ InputBox = { fwidth = 15, } -function InputBox:addString(str) - for i = 1, #str do - self:addChar(str:sub(i,i)) - end -end - function InputBox:refreshText() -- clear previous painted text fb.bb:paintRect(140, self.input_start_y-19, @@ -129,11 +123,13 @@ function InputBox:input(ypos, height, title, d_text) w = fb.bb:getWidth() - 40 h = height - 45 self:drawBox(ypos, w, h, title) - self.cursor:draw() if d_text then self.input_string = d_text - self:addString(self.input_string) + 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 @@ -242,7 +238,7 @@ function InputBox:input(ypos, height, title, d_text) else self:delChar() end - elseif ev.code == KEY_BACK then + elseif ev.code == KEY_BACK or ev.code == KEY_HOME then self.input_string = nil break end From 9eff3e322446ba0ad4868138552ee7c2b0d82492 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 15 Mar 2012 22:18:27 +0800 Subject: [PATCH 004/183] mod: delete debug lines in inputbox.lua --- inputbox.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/inputbox.lua b/inputbox.lua index a7cdc8eca..69789a891 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -136,7 +136,7 @@ function InputBox:input(ypos, height, title, d_text) local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - local secs, usecs = util.gettime() + --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 @@ -243,9 +243,9 @@ function InputBox:input(ypos, height, title, d_text) break 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) + --local nsecs, nusecs = util.gettime() + --local dur = (nsecs - secs) * 1000000 + nusecs - usecs + --print("E: T="..ev.type.." V="..ev.value.." C="..ev.code.." DUR="..dur) end -- if end -- while From 46de106e8a7aeb3d64453c59cae69de3106e2f33 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 16 Mar 2012 14:23:09 +0800 Subject: [PATCH 005/183] fix: clear input_text before input result return --- inputbox.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inputbox.lua b/inputbox.lua index 69789a891..65321e0d6 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -249,5 +249,7 @@ function InputBox:input(ypos, height, title, d_text) end -- if end -- while - return self.input_string + local return_str = self.input_string + self.input_string = "" + return return_str end From baf98c0fcdb9064a46b25650e31ebb292c59b8cd Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 16 Mar 2012 17:23:25 +0800 Subject: [PATCH 006/183] add: POC getPageText method in djvu.c --- djvu.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/djvu.c b/djvu.c index d2b804662..07c9fe9e3 100644 --- a/djvu.c +++ b/djvu.c @@ -305,6 +305,129 @@ static int getUsedBBox(lua_State *L) { return 4; } + +/* + * Return a table like following: + * { + * { -- a line entry + * x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089, + * { + * {word="This", x0=377, y0=4857, x1=2427, y1=5089}, + * {word="is", x0=377, y0=4857, x1=2427, y1=5089}, + * {word="Word", x0=377, y0=4857, x1=2427, y1=5089}, + * {word="List", x0=377, y0=4857, x1=2427, y1=5089}, + * }, + * }, + * + * { -- an other line entry + * x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089, + * { + * {word="This", x0=377, y0=4857, x1=2427, y1=5089}, + * {word="is", 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, + 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); + + 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, i); + lua_newtable(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 */ + 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, j); + lua_newtable(L); + + /* 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 table */ + lua_settable(L, -3); + } /* end of for (j) */ + + /* set line entry to 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) { @@ -415,6 +538,7 @@ static const struct luaL_reg djvudocument_meth[] = { {"openPage", openPage}, {"getPages", getNumberOfPages}, {"getTOC", getTableOfContent}, + {"getPageText", getPageText}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} From 6441c34040fc2e39c893635dfcc6e8470ae233e7 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 17 Mar 2012 10:00:45 +0800 Subject: [PATCH 007/183] fix: bug in page text table construction --- djvu.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/djvu.c b/djvu.c index 07c9fe9e3..32b5fb308 100644 --- a/djvu.c +++ b/djvu.c @@ -310,23 +310,22 @@ static int getUsedBBox(lua_State *L) { * Return a table like following: * { * { -- a line entry - * x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089, - * { + * words = { * {word="This", x0=377, y0=4857, x1=2427, y1=5089}, * {word="is", x0=377, y0=4857, x1=2427, y1=5089}, * {word="Word", x0=377, y0=4857, x1=2427, y1=5089}, * {word="List", x0=377, y0=4857, x1=2427, y1=5089}, * }, + * x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089, * }, * * { -- an other line entry - * x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089, - * { + * words = { * {word="This", x0=377, y0=4857, x1=2427, y1=5089}, * {word="is", x0=377, y0=4857, x1=2427, y1=5089}, * }, + * x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089, * }, - * * } */ static int getPageText(lua_State *L) { @@ -334,7 +333,7 @@ static int getPageText(lua_State *L) { int pageno = luaL_checkint(L, 2); miniexp_t sexp, se_line, se_word; - int i = 1, j = 1, + int i = 1, j = 1, counter = 1, nr_line = 0, nr_word = 0; const char *word = NULL; @@ -343,7 +342,6 @@ static int getPageText(lua_State *L) { handle(L, doc->context, True); } - /* throuw page info and obtain lines info, after this, sexp's entries * are lines. */ sexp = miniexp_cdr(sexp); @@ -381,7 +379,10 @@ static int getPageText(lua_State *L) { lua_pushnumber(L, miniexp_to_int(miniexp_nth(4, se_line))); lua_settable(L, -3); + lua_pushstring(L, "words"); + lua_newtable(L); /* now loop through each word in the line */ + counter = 1; for(j = 1; j <= nr_word; j++) { /* retrive one word entry */ se_word = miniexp_nth(j, se_line); @@ -392,8 +393,9 @@ static int getPageText(lua_State *L) { } /* create table that contains info for a word */ - lua_pushnumber(L, j); + lua_pushnumber(L, counter); lua_newtable(L); + counter++; /* set word info */ lua_pushstring(L, "x0"); @@ -416,12 +418,14 @@ static int getPageText(lua_State *L) { lua_pushstring(L, word); lua_settable(L, -3); - - /* set word entry to table */ + /* set word entry to "words" table */ lua_settable(L, -3); } /* end of for (j) */ - /* set line entry to table */ + /* set "words" table to line entry table */ + lua_settable(L, -3); + + /* set line entry to page text table */ lua_settable(L, -3); } /* end of for (i) */ From 84c823073008499c76486ebacb39963e4732ffbc Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 17 Mar 2012 12:17:20 +0800 Subject: [PATCH 008/183] POC of text highlight in current page! --- djvu.c | 14 ++++++++------ unireader.lua | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/djvu.c b/djvu.c index 32b5fb308..eb64a5fa7 100644 --- a/djvu.c +++ b/djvu.c @@ -286,7 +286,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); @@ -333,7 +333,7 @@ static int getPageText(lua_State *L) { int pageno = luaL_checkint(L, 2); miniexp_t sexp, se_line, se_word; - int i = 1, j = 1, counter = 1, + int i = 1, j = 1, counter_l = 1, counter_w=1, nr_line = 0, nr_word = 0; const char *word = NULL; @@ -350,6 +350,7 @@ static int getPageText(lua_State *L) { /* 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); @@ -359,8 +360,9 @@ static int getPageText(lua_State *L) { } /* subtable that contains words in a line */ - lua_pushnumber(L, i); + lua_pushnumber(L, counter_l); lua_newtable(L); + counter_l++; /* set line position */ lua_pushstring(L, "x0"); @@ -382,7 +384,7 @@ static int getPageText(lua_State *L) { lua_pushstring(L, "words"); lua_newtable(L); /* now loop through each word in the line */ - counter = 1; + counter_w = 1; for(j = 1; j <= nr_word; j++) { /* retrive one word entry */ se_word = miniexp_nth(j, se_line); @@ -393,9 +395,9 @@ static int getPageText(lua_State *L) { } /* create table that contains info for a word */ - lua_pushnumber(L, counter); + lua_pushnumber(L, counter_w); lua_newtable(L); - counter++; + counter_w++; /* set word info */ lua_pushstring(L, "x0"); diff --git a/unireader.lua b/unireader.lua index c81262b89..98a196312 100644 --- a/unireader.lua +++ b/unireader.lua @@ -32,9 +32,12 @@ UniReader = { -- gamma setting: globalgamma = 1.0, -- GAMMA_NO_GAMMA - -- size of current page for current zoom level in pixels + -- cached tile size fullwidth = 0, fullheight = 0, + -- size of current page for current zoom level in pixels + cur_full_width = 0, + cur_full_height = 0, offset_x = 0, offset_y = 0, min_offset_x = 0, @@ -180,7 +183,7 @@ function UniReader:draworcache(no, preCache) -- ideally, this should be factored out and only be called when needed (TODO) local page = self.doc:openPage(no) - local dc = self:setzoom(page) + local dc = self:setzoom(page, preCache) -- offset_x_in_page & offset_y_in_page is the offset within zoomed page -- they are always positive. @@ -285,7 +288,7 @@ function UniReader:clearcache() end -- set viewer state according to zoom state -function UniReader:setzoom(page) +function UniReader:setzoom(page, preCache) local dc = self.newDC() local pwidth, pheight = page:getSize(self.nulldc) print("# page::getSize "..pwidth.."*"..pheight); @@ -415,6 +418,10 @@ function UniReader:setzoom(page) dc:setRotate(self.globalrotate); self.fullwidth, self.fullheight = page:getSize(dc) + if not preCache then -- save current page fullsize + self.cur_full_width = self.fullwidth + self.cur_full_height = self.fullheight + end self.min_offset_x = fb.bb:getWidth() - self.fullwidth self.min_offset_y = fb.bb:getHeight() - self.fullheight if(self.min_offset_x > 0) then @@ -717,6 +724,31 @@ function UniReader:showJumpStack() end end +function UniReader:highLightText() + local t = self.doc:getPageText(self.pageno) + + local function isInScreenRange(v) + return (self.cur_full_height-(v.y0*self.globalzoom) <= + -self.offset_y + width) and + (self.cur_full_height-(v.y1*self.globalzoom) >= + -self.offset_y) + end + + for k1,v1 in ipairs(t) do + local words = v1.words + for k,v in ipairs(words) do + if isInScreenRange(v) then + fb.bb:paintRect( + v.x0*self.globalzoom, + self.offset_y+self.cur_full_height-(v.y1*self.globalzoom), + (v.x1-v.x0)*self.globalzoom, + (v.y1-v.y0)*self.globalzoom, 15) + end -- EOF if isInScreenRange + end -- EOF for words + end -- EOF for lines + fb:refresh(0) +end + function UniReader:showMenu() local ypos = height - 50 local load_percent = (self.pageno / self.doc:getPages()) @@ -860,6 +892,8 @@ function UniReader:inputloop() else self:setrotate( self.globalrotate - 10 ) end + elseif ev.code == KEY_N then + self:highLightText() elseif ev.code == KEY_HOME then if Keys.shiftmode or Keys.altmode then -- signal quit From e7f0a8bddbd79b4605b9b9fad1d5cc1ebd00cc11 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 19 Mar 2012 11:05:23 +0800 Subject: [PATCH 009/183] mod: rewrite highlight feature waiting for integrate test with cursor support --- djvureader.lua | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ unireader.lua | 25 ++-------------------- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index e929f765d..fed95555d 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -17,3 +17,61 @@ function DJVUReader:open(filename) self.doc = djvu.openDocument(filename) return self:loadSettings(filename) end + +function DJVUReader:_isWordInScreenRange(w) + return (self.cur_full_height-(w.y0*self.globalzoom) <= + -self.offset_y + width) and + (self.cur_full_height-(w.y1*self.globalzoom) >= + -self.offset_y) +end + +function DJVUReader:_genTextIter(text, l0, w0, l1, w1) + local word_items = {} + local count = 0 + local l = l0 + local w = w0 + local tmp_w1 = 0 + + -- build item table + while l <= l1 do + local words = text[l].words + + if l == l1 then + tmp_w1 = w1 + else + tmp_w1 = #words + end + + while w <= tmp_w1 do + if self:_isWordInScreenRange(words[w]) then + table.insert(word_items, words[w]) + end -- EOF if isInScreenRange + w = w + 1 + end -- EOF while words + -- goto next line, reset j + w = 1 + l = l + 1 + end -- EOF for while + + return function() count = count + 1 return word_items[count] end +end + +function DJVUReader:_drawTextHighLight(text_iter) + for i in text_iter do + fb.bb:paintRect( + i.x0*self.globalzoom, + self.offset_y+self.cur_full_height-(i.y1*self.globalzoom), + (i.x1-i.x0)*self.globalzoom, + (i.y1-i.y0)*self.globalzoom, 15) + end -- EOF for +end + +function DJVUReader:startHighLightMode() + local t = self.doc:getPageText(self.pageno) + + --self:_drawTextHighLight(self:_genTextIter(t, 1, 1, #t, #(t[#t].words))) + -- highlight the first line + self:_drawTextHighLight(self:_genTextIter(t, 1, 1, 1, #(t[1].words))) + fb:refresh(0) +end + diff --git a/unireader.lua b/unireader.lua index 98a196312..0a198bcf9 100644 --- a/unireader.lua +++ b/unireader.lua @@ -725,28 +725,7 @@ function UniReader:showJumpStack() end function UniReader:highLightText() - local t = self.doc:getPageText(self.pageno) - - local function isInScreenRange(v) - return (self.cur_full_height-(v.y0*self.globalzoom) <= - -self.offset_y + width) and - (self.cur_full_height-(v.y1*self.globalzoom) >= - -self.offset_y) - end - - for k1,v1 in ipairs(t) do - local words = v1.words - for k,v in ipairs(words) do - if isInScreenRange(v) then - fb.bb:paintRect( - v.x0*self.globalzoom, - self.offset_y+self.cur_full_height-(v.y1*self.globalzoom), - (v.x1-v.x0)*self.globalzoom, - (v.y1-v.y0)*self.globalzoom, 15) - end -- EOF if isInScreenRange - end -- EOF for words - end -- EOF for lines - fb:refresh(0) + return end function UniReader:showMenu() @@ -893,7 +872,7 @@ function UniReader:inputloop() self:setrotate( self.globalrotate - 10 ) end elseif ev.code == KEY_N then - self:highLightText() + self:startHighLightMode() elseif ev.code == KEY_HOME then if Keys.shiftmode or Keys.altmode then -- signal quit From 44d7d6cd2963d58168e2dcd84f3fe81d1e6336e5 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 19 Mar 2012 20:43:52 +0800 Subject: [PATCH 010/183] fix: handle -1 index when deleting characters in inputbox --- inputbox.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/inputbox.lua b/inputbox.lua index 65321e0d6..e30e88507 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -60,11 +60,13 @@ function InputBox:delChar() 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 - local cur_index = (self.cursor.x_pos + 3 - self.input_start_x) - / self.fwidth self.input_string = self.input_string:sub(0,cur_index-1).. self.input_string:sub(cur_index+1, -1) self:refreshText() From 7bbc5b5ed205e54eb962cc8fe81bf23185d1b5a1 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 19 Mar 2012 21:22:45 +0800 Subject: [PATCH 011/183] highlight with cursor demo --- djvureader.lua | 124 ++++++++++++++++++++++++++++++++++++++++++++++--- reader.lua | 1 + 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index fed95555d..755727d6a 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -25,6 +25,16 @@ function DJVUReader:_isWordInScreenRange(w) -self.offset_y) end +------------------------------------------------ +-- @text text object returned from doc:getPageText() +-- @l0 start line +-- @w0 start word +-- @l1 end line +-- @w1 end word +-- +-- get words from the w0th word in l0th line +-- to w1th word in l1th line (not included). +------------------------------------------------ function DJVUReader:_genTextIter(text, l0, w0, l1, w1) local word_items = {} local count = 0 @@ -32,12 +42,21 @@ function DJVUReader:_genTextIter(text, l0, w0, l1, w1) local w = w0 local tmp_w1 = 0 + print(l0, w0, l1, w1) + + if l0 < 1 or w0 < 1 or l0 > l1 then + return function() return nil end + end + -- build item table while l <= l1 do local words = text[l].words if l == l1 then - tmp_w1 = w1 + tmp_w1 = w1 - 1 + if tmp_w1 == 0 then + break + end else tmp_w1 = #words end @@ -58,7 +77,7 @@ end function DJVUReader:_drawTextHighLight(text_iter) for i in text_iter do - fb.bb:paintRect( + fb.bb:invertRect( i.x0*self.globalzoom, self.offset_y+self.cur_full_height-(i.y1*self.globalzoom), (i.x1-i.x0)*self.globalzoom, @@ -69,9 +88,102 @@ end function DJVUReader:startHighLightMode() local t = self.doc:getPageText(self.pageno) - --self:_drawTextHighLight(self:_genTextIter(t, 1, 1, #t, #(t[#t].words))) - -- highlight the first line - self:_drawTextHighLight(self:_genTextIter(t, 1, 1, 1, #(t[1].words))) - fb:refresh(0) + local function _nextWord(t, cur_l, cur_w) + local new_l = cur_l + local new_w = cur_w + + if new_w >= #(t[new_l].words) then + -- already the last word, goto next line + new_l = new_l + 1 + if new_l > #t or #(t[new_l].words) == 0 then + return cur_l, cur_w + end + new_w = 1 + else + -- simply move to next word in the same line + new_w = new_w + 1 + end + + return new_l, new_w + end + + local function _prevWord(t, cur_l, cur_w) + local new_l = cur_l + local new_w = cur_w + + if new_w == 1 then + -- already the first word, goto previous line + new_l = new_l - 1 + if new_l == 0 or #(t[new_l].words) == 0 then + return cur_l, cur_w + end + new_w = #(t[new_l].words) + 1 + else + -- simply move to previous word in the same line + new_w = new_w - 1 + end + + return new_l, new_w + end + + local function _nextLine(t, cur_l, cur_w) + local new_l = cur_l + local new_w = cur_w + + if new_l >= #t then + return cur_l, cur_w + end + + new_l = new_l + 1 + new_w = math.min(new_w, #t[new_l].words+1) + + return new_l, new_w + end + + local function _prevLine(t, cur_l, cur_w) + local new_l = cur_l + local new_w = cur_w + + if new_l == 1 then + return cur_l, cur_w + end + + new_l = new_l - 1 + new_w = math.min(new_w, #t[new_l].words+1) + + return new_l, new_w + end + + + -- next to be marked word position + local cur_l = 1 + --local cur_w = #(t[1].words) + local cur_w = 1 + local new_l = 1 + local new_w = 1 + local iter + + while true do + local ev = input.waitForEvent() + ev.code = adjustKeyEvents(ev) + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + if ev.code == KEY_FW_RIGHT then + new_l, new_w = _nextWord(t, cur_l, cur_w) + iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) + elseif ev.code == KEY_FW_LEFT then + new_l, new_w = _prevWord(t, cur_l, cur_w) + iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) + elseif ev.code == KEY_FW_DOWN then + new_l, new_w = _nextLine(t, cur_l, cur_w) + iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) + elseif ev.code == KEY_FW_UP then + new_l, new_w = _prevLine(t, cur_l, cur_w) + iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) + end + self:_drawTextHighLight(iter) + fb:refresh(0) + cur_l, cur_w = new_l, new_w + end + end end diff --git a/reader.lua b/reader.lua index 9c18d3cee..4b582d9f9 100755 --- a/reader.lua +++ b/reader.lua @@ -140,6 +140,7 @@ if ARGV[optind] and lfs.attributes(ARGV[optind], "mode") == "directory" then else if file ~= nil then running = openFile(file) + print(file) else running = false end From ac8206fa95e159bef28dc6246f16b26db4764988 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 20 Mar 2012 01:30:33 +0800 Subject: [PATCH 012/183] first demo for pan by page highlight --- djvureader.lua | 136 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 107 insertions(+), 29 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 755727d6a..808755371 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -19,12 +19,21 @@ function DJVUReader:open(filename) end function DJVUReader:_isWordInScreenRange(w) - return (self.cur_full_height-(w.y0*self.globalzoom) <= - -self.offset_y + width) and - (self.cur_full_height-(w.y1*self.globalzoom) >= - -self.offset_y) + -- y axel in djvulibre starts from bottom + return (w ~= nil) and ( + ( self.cur_full_height-(w.y0*self.globalzoom) <= + -self.offset_y + height ) and + ( self.cur_full_height-(w.y1*self.globalzoom) >= + -self.offset_y )) end +function DJVUReader:_isLastWordInPage(t, l, w) + return (l == #t) and (w == #(t[l].words)) +end + +function DJVUReader:_isFirstWordInPage(t, l, w) + return (l == 1) and (w == 1) +end ------------------------------------------------ -- @text text object returned from doc:getPageText() -- @l0 start line @@ -88,17 +97,20 @@ end function DJVUReader:startHighLightMode() local t = self.doc:getPageText(self.pageno) - local function _nextWord(t, cur_l, cur_w) + local function _posToNextWord(t, cur_l, cur_w) local new_l = cur_l local new_w = cur_w if new_w >= #(t[new_l].words) then - -- already the last word, goto next line - new_l = new_l + 1 - if new_l > #t or #(t[new_l].words) == 0 then - return cur_l, cur_w + if new_l == #t then + -- word to mark is the last word in last line + return new_l, #(t[new_l].words)+1 + else + -- word to mark is not the last word in last line, + -- goto next line + new_l = new_l + 1 + new_w = 1 end - new_w = 1 else -- simply move to next word in the same line new_w = new_w + 1 @@ -107,7 +119,7 @@ function DJVUReader:startHighLightMode() return new_l, new_w end - local function _prevWord(t, cur_l, cur_w) + local function _posToPrevWord(t, cur_l, cur_w) local new_l = cur_l local new_w = cur_w @@ -117,7 +129,7 @@ function DJVUReader:startHighLightMode() if new_l == 0 or #(t[new_l].words) == 0 then return cur_l, cur_w end - new_w = #(t[new_l].words) + 1 + new_w = #(t[new_l].words) else -- simply move to previous word in the same line new_w = new_w - 1 @@ -126,21 +138,22 @@ function DJVUReader:startHighLightMode() return new_l, new_w end - local function _nextLine(t, cur_l, cur_w) + local function _posToNextLine(t, cur_l, cur_w) local new_l = cur_l local new_w = cur_w if new_l >= #t then - return cur_l, cur_w + -- already last line, jump to line end instead + return new_l, #(t[new_l].words)+1 end new_l = new_l + 1 - new_w = math.min(new_w, #t[new_l].words+1) + new_w = math.min(new_w, #t[new_l].words) return new_l, new_w end - local function _prevLine(t, cur_l, cur_w) + local function _posToPrevLine(t, cur_l, cur_w) local new_l = cur_l local new_w = cur_w @@ -149,12 +162,14 @@ function DJVUReader:startHighLightMode() end new_l = new_l - 1 - new_w = math.min(new_w, #t[new_l].words+1) + new_w = math.min(new_w, #t[new_l].words) return new_l, new_w end + local start_l = 1 + local start_w = 1 -- next to be marked word position local cur_l = 1 --local cur_w = #(t[1].words) @@ -162,24 +177,87 @@ function DJVUReader:startHighLightMode() local new_l = 1 local new_w = 1 local iter + local meet_page_end = false + local meet_page_start = false while true do local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - if ev.code == KEY_FW_RIGHT then - new_l, new_w = _nextWord(t, cur_l, cur_w) - iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) - elseif ev.code == KEY_FW_LEFT then - new_l, new_w = _prevWord(t, cur_l, cur_w) - iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) - elseif ev.code == KEY_FW_DOWN then - new_l, new_w = _nextLine(t, cur_l, cur_w) - iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) + if ev.code == KEY_FW_LEFT then + if self:_isFirstWordInPage(t, cur_l, cur_w) then + iter = function() return nil end + else + new_l, new_w = _posToPrevWord(t, cur_l, cur_w) + if not self:_isWordInScreenRange(t[new_l].words[new_w]) then + -- goto next view of current page + local pageno = self:prevView() + self:goto(pageno) + cur_l = start_l + cur_w = start_w + end + iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) + meet_page_end = false + end + elseif ev.code == KEY_FW_RIGHT then + if meet_page_end then + iter = function() return nil end + else + new_l, new_w = _posToNextWord(t, cur_l, cur_w) + if not self:_isWordInScreenRange(t[new_l].words[new_w]) then + if self:_isLastWordInPage(t, new_l, new_w-1) then + -- meet the end of page, mark it + meet_page_end = true + else + -- goto next view of current page + local pageno = self:nextView() + self:goto(pageno) + cur_l = start_l + cur_w = start_w + end + end + iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) + meet_page_start = false + end elseif ev.code == KEY_FW_UP then - new_l, new_w = _prevLine(t, cur_l, cur_w) - iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) - end + if self:_isFirstWordInPage(t, cur_l, cur_w) then + iter = function() return nil end + else + new_l, new_w = _posToPrevLine(t, cur_l, cur_w) + if not self:_isWordInScreenRange(t[new_l].words[new_w]) then + -- goto next view of current page + local pageno = self:prevView() + self:goto(pageno) + cur_l = start_l + cur_w = start_w + end + iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) + meet_page_end = false + end + elseif ev.code == KEY_FW_DOWN then + if meet_page_end then + -- already at the end of page, we don't do a pageturn + -- so do noting here + iter = function() return nil end + else + new_l, new_w = _posToNextLine(t, cur_l, cur_w) + if not self:_isWordInScreenRange(t[new_l].words[new_w]) then + if self:_isLastWordInPage(t, new_l, new_w-1) then + -- meet the end of page, mark it + meet_page_end = true + else + -- goto next view of current page + local pageno = self:nextView() + self:goto(pageno) + cur_l = start_l + cur_w = start_w + end + end + iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) + meet_page_start = false + end + end -- EOF if keyevent + self:_drawTextHighLight(iter) fb:refresh(0) cur_l, cur_w = new_l, new_w From a185f238ebd230d77f2fd742c5a3195d2fa770c3 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 20 Mar 2012 16:42:22 +0800 Subject: [PATCH 013/183] mod: rewrite highlight feature --- djvu.c | 82 ++++++++---- djvureader.lua | 353 +++++++++++++++++++++++++++++++------------------ graphics.lua | 24 +++- unireader.lua | 2 +- 4 files changed, 301 insertions(+), 160 deletions(-) diff --git a/djvu.c b/djvu.c index 3dea71698..789743483 100644 --- a/djvu.c +++ b/djvu.c @@ -247,6 +247,19 @@ static int getUsedBBox(lua_State *L) { * x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089, * }, * } + * + * 5 words in two lines + * { + * 1 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, + * 2 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, + * 3 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, + * 4 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, + * 5 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, + * lines = { + * 1 = {last = 2, x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089}, + * 2 = {last = 5, x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089}, + * } + * } */ static int getPageText(lua_State *L) { DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); @@ -267,10 +280,16 @@ static int getPageText(lua_State *L) { sexp = miniexp_cdr(sexp); /* get number of lines in a page */ nr_line = miniexp_length(sexp); - /* table that contains all the lines */ + /* the outer table */ lua_newtable(L); + /* create lines subtable */ + lua_pushstring(L, "lines"); + lua_newtable(L); + lua_settable(L, -3); + counter_l = 1; + counter_w = 1; for(i = 1; i <= nr_line; i++) { /* retrive one line entry */ se_line = miniexp_nth(i, sexp); @@ -279,32 +298,7 @@ static int getPageText(lua_State *L) { 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); - - lua_pushstring(L, "words"); - lua_newtable(L); /* 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); @@ -340,14 +334,44 @@ static int getPageText(lua_State *L) { lua_pushstring(L, word); lua_settable(L, -3); - /* set word entry to "words" table */ + /* set word entry to outer table */ lua_settable(L, -3); } /* end of for (j) */ - /* set "words" table to line entry table */ + /* get lines table from outer table */ + lua_pushstring(L, "lines"); + lua_getfield(L, -2, "lines"); + + /* subtable that contains info for 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); + + lua_pushstring(L, "last"); + lua_pushnumber(L, counter_w-1); + lua_settable(L, -3); + + /* set line entry to lines subtable */ lua_settable(L, -3); - /* set line entry to page text table */ + /* set lines subtable back to outer table */ lua_settable(L, -3); } /* end of for (i) */ diff --git a/djvureader.lua b/djvureader.lua index 160c5a7ed..cf17ca108 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -22,6 +22,18 @@ function DJVUReader:_isWordInScreenRange(w) -self.offset_y )) end +function DJVUReader:_toggleWordHighLightByEnds(t, end1, end2) + if end1 > end2 then + end1, end2 = end2, end1 + end + + for i=end1, end2, 1 do + if self:_isWordInScreenRange(t[i]) then + self:_toggleWordHighLight(t[i]) + end + end +end + function DJVUReader:_isLastWordInPage(t, l, w) return (l == #t) and (w == #(t[l].words)) end @@ -79,184 +91,273 @@ function DJVUReader:_genTextIter(text, l0, w0, l1, w1) return function() count = count + 1 return word_items[count] end end -function DJVUReader:_drawTextHighLight(text_iter) - for i in text_iter do - fb.bb:invertRect( - i.x0*self.globalzoom, - self.offset_y+self.cur_full_height-(i.y1*self.globalzoom), - (i.x1-i.x0)*self.globalzoom, - (i.y1-i.y0)*self.globalzoom, 15) - end -- EOF for +function DJVUReader:getScreenPosByPagePos() +end + +function DJVUReader:_toggleWordHighLight(w) + fb.bb:invertRect( + w.x0*self.globalzoom, + self.offset_y+self.cur_full_height-(w.y1*self.globalzoom), + (w.x1-w.x0)*self.globalzoom, + (w.y1-w.y0)*self.globalzoom, 15) end function DJVUReader:startHighLightMode() local t = self.doc:getPageText(self.pageno) - local function _posToNextWord(t, cur_l, cur_w) - local new_l = cur_l - local new_w = cur_w - - if new_w >= #(t[new_l].words) then - if new_l == #t then - -- word to mark is the last word in last line - return new_l, #(t[new_l].words)+1 - else - -- word to mark is not the last word in last line, - -- goto next line - new_l = new_l + 1 - new_w = 1 + --print(dump(t)) + + local function _getLineByWord(t, cur_w) + for k,l in ipairs(t.lines) do + if l.last >= cur_w then + return k end - else - -- simply move to next word in the same line - new_w = new_w + 1 end - - return new_l, new_w end - local function _posToPrevWord(t, cur_l, cur_w) - local new_l = cur_l - local new_w = cur_w - - if new_w == 1 then - -- already the first word, goto previous line - new_l = new_l - 1 - if new_l == 0 or #(t[new_l].words) == 0 then - return cur_l, cur_w + local function _findFirstWordInView(t) + for k,v in ipairs(t) do + if self:_isWordInScreenRange(v) then + return k end - new_w = #(t[new_l].words) - else - -- simply move to previous word in the same line - new_w = new_w - 1 end - - return new_l, new_w + return nil end - local function _posToNextLine(t, cur_l, cur_w) - local new_l = cur_l - local new_w = cur_w + local function _wordInNextLine(t, cur_w) + local cur_l = _getLineByWord(t, cur_w) + if cur_l == #t.lines then + -- already in last line, return the last word + return t.lines[cur_l].last + else + local next_l_start = t.lines[cur_l].last + 1 + local cur_l_start = 1 + if cur_l ~= 1 then + cur_l_start = t.lines[cur_l-1].last + 1 + end - if new_l >= #t then - -- already last line, jump to line end instead - return new_l, #(t[new_l].words)+1 + cur_w = next_l_start + (cur_w - cur_l_start) + if cur_w > t.lines[cur_l+1].last then + cur_w = t.lines[cur_l+1].last + end + return cur_w end - - new_l = new_l + 1 - new_w = math.min(new_w, #t[new_l].words) - - return new_l, new_w end - local function _posToPrevLine(t, cur_l, cur_w) - local new_l = cur_l - local new_w = cur_w + local function _wordInPrevLine(t, cur_w) + local cur_l = _getLineByWord(t, cur_w) + if cur_l == 1 then + -- already in first line, return 0 + return 0 + else + local prev_l_start = 1 + if cur_l > 2 then + -- previous line is not the first line + prev_l_start = t.lines[cur_l-2].last + 1 + end + local cur_l_start = t.lines[cur_l-1].last + 1 - if new_l == 1 then - return cur_l, cur_w + cur_w = prev_l_start + (cur_w - cur_l_start) + if cur_w > t.lines[cur_l-1].last then + cur_w = t.lines[cur_l-1].last + end + return cur_w end + end - new_l = new_l - 1 - new_w = math.min(new_w, #t[new_l].words) - return new_l, new_w + local start_w = _findFirstWordInView(t) + if not start_w then + print("# no text in current view!") + return end - - local start_l = 1 - local start_w = 1 - -- next to be marked word position - local cur_l = 1 - --local cur_w = #(t[1].words) - local cur_w = 1 - local new_l = 1 + local cur_w = start_w local new_w = 1 - local iter - local meet_page_end = false - local meet_page_start = false + local is_hightlight_mode = false + + self.cursor = Cursor:new { + x_pos = t[cur_w].x1*self.globalzoom, + y_pos = self.offset_y + (self.cur_full_height + - (t[cur_w].y1 * self.globalzoom)), + h = (t[cur_w].y1-t[cur_w].y0)*self.globalzoom, + } + + self.cursor:draw() + fb:refresh(0) while true do local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then if ev.code == KEY_FW_LEFT then - if self:_isFirstWordInPage(t, cur_l, cur_w) then - iter = function() return nil end - else - new_l, new_w = _posToPrevWord(t, cur_l, cur_w) - if not self:_isWordInScreenRange(t[new_l].words[new_w]) then - -- goto next view of current page + if cur_w >= 1 then + new_w = cur_w - 1 + + if new_w ~= 0 and + not self:_isWordInScreenRange(t[new_w]) then + -- word is in previous view local pageno = self:prevView() self:goto(pageno) - cur_l = start_l - cur_w = start_w + else + self.cursor:clear() + end + + -- update cursor + if new_w == 0 then + -- meet top end, must be handled as special case + self.cursor:setHeight((t[1].y1 - t[1].y0) + * self.globalzoom) + self.cursor:moveTo( + t[1].x0*self.globalzoom - self.cursor.w, + self.offset_y + self.cur_full_height + - t[1].y1 * self.globalzoom) + else + self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) + * self.globalzoom) + self.cursor:moveTo(t[new_w].x1*self.globalzoom, + self.offset_y + self.cur_full_height + - (t[new_w].y1*self.globalzoom)) end - iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) - meet_page_end = false + self.cursor:draw() + + if is_hightlight_mode then + -- update highlight + if new_w ~= 0 and + not self:_isWordInScreenRange(t[new_w]) then + self:_toggleWordHighLightByEnds(t, start_w, new_w) + else + self:_toggleWordHighLight(t[new_w+1]) + end + end + + cur_w = new_w end elseif ev.code == KEY_FW_RIGHT then - if meet_page_end then - iter = function() return nil end - else - new_l, new_w = _posToNextWord(t, cur_l, cur_w) - if not self:_isWordInScreenRange(t[new_l].words[new_w]) then - if self:_isLastWordInPage(t, new_l, new_w-1) then - -- meet the end of page, mark it - meet_page_end = true - else - -- goto next view of current page + -- only highlight word in current page + if cur_w < #t then + new_w = cur_w + 1 + + if not self:_isWordInScreenRange(t[new_w]) then local pageno = self:nextView() self:goto(pageno) - cur_l = start_l - cur_w = start_w + else + self.cursor:clear() + end + + -- update cursor + self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) + * self.globalzoom) + self.cursor:moveTo(t[new_w].x1*self.globalzoom, + self.offset_y + self.cur_full_height + - (t[new_w].y1 * self.globalzoom)) + self.cursor:draw() + + if is_hightlight_mode then + -- update highlight + if not self:_isWordInScreenRange(t[new_w]) then + -- word to highlight is in next view + self:_toggleWordHighLightByEnds(t, start_w, new_w) + else + self:_toggleWordHighLight(t[new_w]) end end - iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) - meet_page_start = false + + cur_w = new_w end elseif ev.code == KEY_FW_UP then - if self:_isFirstWordInPage(t, cur_l, cur_w) then - iter = function() return nil end - else - new_l, new_w = _posToPrevLine(t, cur_l, cur_w) - if not self:_isWordInScreenRange(t[new_l].words[new_w]) then + new_w = _wordInPrevLine(t, cur_w) + + if new_w ~= 0 and + not self:_isWordInScreenRange(t[new_w]) then -- goto next view of current page local pageno = self:prevView() self:goto(pageno) - cur_l = start_l - cur_w = start_w + else + -- no need to jump to next view, clear previous cursor + self.cursor:clear() end - iter = self:_genTextIter(t, new_l, new_w, cur_l, cur_w) - meet_page_end = false - end + + if new_w == 0 then + -- meet top end, must be handled as special case + self.cursor:setHeight((t[1].y1 - t[1].y0) + * self.globalzoom) + self.cursor:moveTo( + t[1].x0*self.globalzoom - self.cursor.w, + self.offset_y + self.cur_full_height + - t[1].y1 * self.globalzoom) + else + self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) + * self.globalzoom) + self.cursor:moveTo(t[new_w].x1*self.globalzoom, + self.offset_y + self.cur_full_height + - (t[new_w].y1*self.globalzoom)) + end + self.cursor:draw() + + if is_hightlight_mode then + -- update highlight + if new_w ~= 0 and + not self:_isWordInScreenRange(t[new_w]) then + -- word is in previous view + self:_toggleWordHighLightByEnds(t, start_w, new_w) + else + for i=new_w+1, cur_w, 1 do + self:_toggleWordHighLight(t[i]) + end + end + end + + cur_w = new_w elseif ev.code == KEY_FW_DOWN then - if meet_page_end then - -- already at the end of page, we don't do a pageturn - -- so do noting here - iter = function() return nil end + new_w = _wordInNextLine(t, cur_w) + + if not self:_isWordInScreenRange(t[new_w]) then + -- goto next view of current page + local pageno = self:nextView() + self:goto(pageno) else - new_l, new_w = _posToNextLine(t, cur_l, cur_w) - if not self:_isWordInScreenRange(t[new_l].words[new_w]) then - if self:_isLastWordInPage(t, new_l, new_w-1) then - -- meet the end of page, mark it - meet_page_end = true - else - -- goto next view of current page - local pageno = self:nextView() - self:goto(pageno) - cur_l = start_l - cur_w = start_w + -- no need to jump to next view, clear previous cursor + self.cursor:clear() + end + + -- update cursor + self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) + * self.globalzoom) + self.cursor:moveTo(t[new_w].x1*self.globalzoom, + self.offset_y + self.cur_full_height + - (t[new_w].y1*self.globalzoom)) + self.cursor:draw() + + if is_hightlight_mode then + -- update highlight + if not self:_isWordInScreenRange(t[new_w]) then + -- redraw from start because of page refresh + self:_toggleWordHighLightByEnds(t, start_w, new_w) + else + -- word in next is in current view, just highlight it + for i=cur_w+1, new_w, 1 do + self:_toggleWordHighLight(t[i]) end end - iter = self:_genTextIter(t, cur_l, cur_w, new_l, new_w) - meet_page_start = false end - end -- EOF if keyevent - self:_drawTextHighLight(iter) + cur_w = new_w + elseif ev.code == KEY_FW_PRESS then + if not is_hightlight_mode then + is_hightlight_mode = true + start_w = cur_w + else -- pressed in highlight mode, record selected text + if start_w < cur_w then + self:_toggleWordHighLightByEnds(t, start_w+1, cur_w) + else + self:_toggleWordHighLightByEnds(t, cur_w+1, start_w) + end + is_hightlight_mode = false + end + end -- EOF if keyevent fb:refresh(0) - cur_l, cur_w = new_l, new_w - end + end -- EOF while end end diff --git a/graphics.lua b/graphics.lua index f12510252..d4741c23e 100644 --- a/graphics.lua +++ b/graphics.lua @@ -48,16 +48,20 @@ 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.h = o.h or self.h - - o.w = o.h / 3 - o.line_w = math.floor(o.h / 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 / 10) +end + function Cursor:_draw(x, y) local body_h = self.h - self.line_w -- paint upper horizontal line @@ -96,6 +100,18 @@ function Cursor:moveAndDraw(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) diff --git a/unireader.lua b/unireader.lua index 6bc70a9be..9360fcf0d 100644 --- a/unireader.lua +++ b/unireader.lua @@ -183,7 +183,7 @@ function UniReader:draworcache(no, preCache) -- TODO: error handling return nil end - local dc = self:setzoom(page) + local dc = self:setzoom(page, preCache) -- offset_x_in_page & offset_y_in_page is the offset within zoomed page -- they are always positive. From 1e2d7f62ac1996305aed35fd57a79020e7ddf75b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 21 Mar 2012 10:37:18 +0800 Subject: [PATCH 014/183] record and save highlight table --- djvureader.lua | 181 ++++++++++++++++++------------------------------- graphics.lua | 12 ++-- unireader.lua | 15 ++++ 3 files changed, 87 insertions(+), 121 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index cf17ca108..770b38922 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -22,7 +22,11 @@ function DJVUReader:_isWordInScreenRange(w) -self.offset_y )) end -function DJVUReader:_toggleWordHighLightByEnds(t, end1, end2) +function DJVUReader:toggleTextHighLight(word_list) + self:_toggleTextHighLightByEnds(word_list, 1, #word_list) +end + +function DJVUReader:_toggleTextHighLightByEnds(t, end1, end2) if end1 > end2 then end1, end2 = end2, end1 end @@ -34,79 +38,27 @@ function DJVUReader:_toggleWordHighLightByEnds(t, end1, end2) end end -function DJVUReader:_isLastWordInPage(t, l, w) - return (l == #t) and (w == #(t[l].words)) -end - -function DJVUReader:_isFirstWordInPage(t, l, w) - return (l == 1) and (w == 1) -end ------------------------------------------------- --- @text text object returned from doc:getPageText() --- @l0 start line --- @w0 start word --- @l1 end line --- @w1 end word --- --- get words from the w0th word in l0th line --- to w1th word in l1th line (not included). ------------------------------------------------- -function DJVUReader:_genTextIter(text, l0, w0, l1, w1) - local word_items = {} - local count = 0 - local l = l0 - local w = w0 - local tmp_w1 = 0 - - print(l0, w0, l1, w1) - - if l0 < 1 or w0 < 1 or l0 > l1 then - return function() return nil end - end - - -- build item table - while l <= l1 do - local words = text[l].words - - if l == l1 then - tmp_w1 = w1 - 1 - if tmp_w1 == 0 then - break - end - else - tmp_w1 = #words - end - - while w <= tmp_w1 do - if self:_isWordInScreenRange(words[w]) then - table.insert(word_items, words[w]) - end -- EOF if isInScreenRange - w = w + 1 - end -- EOF while words - -- goto next line, reset j - w = 1 - l = l + 1 - end -- EOF for while - - return function() count = count + 1 return word_items[count] end -end - -function DJVUReader:getScreenPosByPagePos() -end - function DJVUReader:_toggleWordHighLight(w) + local width = (w.x1-w.x0)*self.globalzoom + local height = (w.y1-w.y0)*self.globalzoom fb.bb:invertRect( - w.x0*self.globalzoom, - self.offset_y+self.cur_full_height-(w.y1*self.globalzoom), - (w.x1-w.x0)*self.globalzoom, - (w.y1-w.y0)*self.globalzoom, 15) + w.x0*self.globalzoom-width*0.05, + self.offset_y+self.cur_full_height-(w.y1*self.globalzoom)-height*0.05, + width*1.1, + height*1.1) +end + +-- remember to clear cursor before calling this +function DJVUReader:drawCursorAfterWord(w) + self.cursor:setHeight((w.y1 - w.y0) * self.globalzoom) + self.cursor:moveTo(w.x1 * self.globalzoom, + self.offset_y + self.cur_full_height - (w.y1 * self.globalzoom)) + self.cursor:draw() end function DJVUReader:startHighLightMode() local t = self.doc:getPageText(self.pageno) - --print(dump(t)) - local function _getLineByWord(t, cur_w) for k,l in ipairs(t.lines) do if l.last >= cur_w then @@ -175,23 +127,25 @@ function DJVUReader:startHighLightMode() local cur_w = start_w local new_w = 1 local is_hightlight_mode = false + local running = true self.cursor = Cursor:new { x_pos = t[cur_w].x1*self.globalzoom, y_pos = self.offset_y + (self.cur_full_height - (t[cur_w].y1 * self.globalzoom)), - h = (t[cur_w].y1-t[cur_w].y0)*self.globalzoom, + h = (t[cur_w].y1 - t[cur_w].y0) * self.globalzoom, + line_width_factor = 4, } - self.cursor:draw() fb:refresh(0) - while true do + while running do local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then if ev.code == KEY_FW_LEFT then if cur_w >= 1 then + local is_next_view = false new_w = cur_w - 1 if new_w ~= 0 and @@ -199,6 +153,7 @@ function DJVUReader:startHighLightMode() -- word is in previous view local pageno = self:prevView() self:goto(pageno) + is_next_view = true else self.cursor:clear() end @@ -212,60 +167,48 @@ function DJVUReader:startHighLightMode() t[1].x0*self.globalzoom - self.cursor.w, self.offset_y + self.cur_full_height - t[1].y1 * self.globalzoom) + self.cursor:draw() else - self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) - * self.globalzoom) - self.cursor:moveTo(t[new_w].x1*self.globalzoom, - self.offset_y + self.cur_full_height - - (t[new_w].y1*self.globalzoom)) + self:drawCursorAfterWord(t[new_w]) end - self.cursor:draw() if is_hightlight_mode then -- update highlight - if new_w ~= 0 and - not self:_isWordInScreenRange(t[new_w]) then - self:_toggleWordHighLightByEnds(t, start_w, new_w) + if new_w ~= 0 and is_next_view then + self:_toggleTextHighLightByEnds(t, start_w, new_w) else self:_toggleWordHighLight(t[new_w+1]) end end - - cur_w = new_w end elseif ev.code == KEY_FW_RIGHT then -- only highlight word in current page if cur_w < #t then + local is_next_view = false new_w = cur_w + 1 if not self:_isWordInScreenRange(t[new_w]) then local pageno = self:nextView() self:goto(pageno) + is_next_view = true else self.cursor:clear() end -- update cursor - self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) - * self.globalzoom) - self.cursor:moveTo(t[new_w].x1*self.globalzoom, - self.offset_y + self.cur_full_height - - (t[new_w].y1 * self.globalzoom)) - self.cursor:draw() + self:drawCursorAfterWord(t[new_w]) if is_hightlight_mode then -- update highlight - if not self:_isWordInScreenRange(t[new_w]) then - -- word to highlight is in next view - self:_toggleWordHighLightByEnds(t, start_w, new_w) + if is_next_view then + self:_toggleTextHighLightByEnds(t, start_w, new_w) else self:_toggleWordHighLight(t[new_w]) end end - - cur_w = new_w end elseif ev.code == KEY_FW_UP then + local is_next_view = false new_w = _wordInPrevLine(t, cur_w) if new_w ~= 0 and @@ -273,67 +216,58 @@ function DJVUReader:startHighLightMode() -- goto next view of current page local pageno = self:prevView() self:goto(pageno) + is_next_view = true else -- no need to jump to next view, clear previous cursor self.cursor:clear() end if new_w == 0 then - -- meet top end, must be handled as special case + -- meet top left end, must be handled as special case self.cursor:setHeight((t[1].y1 - t[1].y0) * self.globalzoom) self.cursor:moveTo( t[1].x0*self.globalzoom - self.cursor.w, self.offset_y + self.cur_full_height - t[1].y1 * self.globalzoom) + self.cursor:draw() else - self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) - * self.globalzoom) - self.cursor:moveTo(t[new_w].x1*self.globalzoom, - self.offset_y + self.cur_full_height - - (t[new_w].y1*self.globalzoom)) + self:drawCursorAfterWord(t[new_w]) end - self.cursor:draw() if is_hightlight_mode then -- update highlight - if new_w ~= 0 and - not self:_isWordInScreenRange(t[new_w]) then + if new_w ~= 0 and is_next_view then -- word is in previous view - self:_toggleWordHighLightByEnds(t, start_w, new_w) + self:_toggleTextHighLightByEnds(t, start_w, new_w) else for i=new_w+1, cur_w, 1 do self:_toggleWordHighLight(t[i]) end end end - - cur_w = new_w elseif ev.code == KEY_FW_DOWN then + local is_next_view = false new_w = _wordInNextLine(t, cur_w) if not self:_isWordInScreenRange(t[new_w]) then -- goto next view of current page local pageno = self:nextView() self:goto(pageno) + is_next_view = true else -- no need to jump to next view, clear previous cursor self.cursor:clear() end -- update cursor - self.cursor:setHeight((t[new_w].y1 - t[new_w].y0) - * self.globalzoom) - self.cursor:moveTo(t[new_w].x1*self.globalzoom, - self.offset_y + self.cur_full_height - - (t[new_w].y1*self.globalzoom)) - self.cursor:draw() + self:drawCursorAfterWord(t[new_w]) if is_hightlight_mode then -- update highlight - if not self:_isWordInScreenRange(t[new_w]) then + if is_next_view then -- redraw from start because of page refresh - self:_toggleWordHighLightByEnds(t, start_w, new_w) + self:_toggleTextHighLightByEnds(t, start_w, new_w) else -- word in next is in current view, just highlight it for i=cur_w+1, new_w, 1 do @@ -341,21 +275,36 @@ function DJVUReader:startHighLightMode() end end end - - cur_w = new_w elseif ev.code == KEY_FW_PRESS then if not is_hightlight_mode then is_hightlight_mode = true start_w = cur_w else -- pressed in highlight mode, record selected text + local first, last = 0 if start_w < cur_w then - self:_toggleWordHighLightByEnds(t, start_w+1, cur_w) + first = start_w + 1 + last = cur_w else - self:_toggleWordHighLightByEnds(t, cur_w+1, start_w) + first = cur_w + 1 + last = start_w + end + --self:_toggleTextHighLightByEnds(t, first, last) + + local hl_item = {} + for i=first,last,1 do + table.insert(hl_item, t[i]) + end + if not self.highlight[self.pageno] then + self.highlight[self.pageno] = {} end + table.insert(self.highlight[self.pageno], hl_item) + is_hightlight_mode = false + running = false + self.cursor:clear() end end -- EOF if keyevent + cur_w = new_w fb:refresh(0) end -- EOF while end diff --git a/graphics.lua b/graphics.lua index d4741c23e..2e357b1ae 100644 --- a/graphics.lua +++ b/graphics.lua @@ -48,6 +48,7 @@ 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 @@ -59,18 +60,19 @@ end function Cursor:setHeight(h) self.h = h self.w = self.h / 3 - self.line_w = math.floor(self.h / 10) + self.line_w = math.floor(self.h / self.line_width_factor) end function Cursor:_draw(x, y) - local body_h = self.h - self.line_w + 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, self.line_w/2) + fb.bb:invertRect(x, y, self.w, up_down_width) -- paint middle vertical line - fb.bb:invertRect(x+(self.w/2)-(self.line_w/2), y+self.line_w/2, + 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+self.line_w/2, self.w, self.line_w/2) + fb.bb:invertRect(x, y + body_h + up_down_width, self.w, up_down_width) end function Cursor:draw() diff --git a/unireader.lua b/unireader.lua index 9360fcf0d..5190c74ed 100644 --- a/unireader.lua +++ b/unireader.lua @@ -72,6 +72,7 @@ UniReader = { pagehash = nil, jump_stack = {}, + highlight = {}, toc = nil, bbox = {}, -- override getUsedBBox @@ -474,6 +475,14 @@ function UniReader:show(no) "), src_off:("..offset_x..", "..offset_y.."), ".. "width:"..width..", height:"..height) fb.bb:blitFrom(bb, dest_x, dest_y, offset_x, offset_y, width, height) + + -- add highlights + if self.highlight[no] then + for k,v in ipairs(self.highlight[no]) do + self:toggleTextHighLight(v) + end + end + if self.rcount == self.rcountmax then print("full refresh") self.rcount = 1 @@ -731,6 +740,11 @@ function UniReader:highLightText() return end + +function UniReader:toggleTextHighLight(word_list) + return +end + function UniReader:showMenu() local ypos = height - 50 local load_percent = (self.pageno / self.doc:getPages()) @@ -1014,6 +1028,7 @@ function UniReader:inputloop() self.settings:savesetting("last_page", self.pageno) self.settings:savesetting("gamma", self.globalgamma) self.settings:savesetting("jumpstack", self.jump_stack) + self.settings:savesetting("highlight", self.highlight) self.settings:savesetting("bbox", self.bbox) self.settings:savesetting("globalzoom", self.globalzoom) self.settings:savesetting("globalzoommode", self.globalzoommode) From 9aa9bc802fb90738843f69f45b956795c1cfc2fe Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 21 Mar 2012 11:20:07 +0800 Subject: [PATCH 015/183] mod: add highlight after merged command module --- unireader.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/unireader.lua b/unireader.lua index 5a1c2755c..be2d97c33 100644 --- a/unireader.lua +++ b/unireader.lua @@ -122,6 +122,9 @@ function UniReader:loadSettings(filename) local jumpstack = self.settings:readSetting("jumpstack") self.jump_stack = jumpstack or {} + local highlight = self.settings:readSetting("highlight") + self.highlight = highlight or {} + local bbox = self.settings:readSetting("bbox") print("# bbox loaded "..dump(bbox)) self.bbox = bbox @@ -831,6 +834,7 @@ function UniReader:inputLoop() self.settings:savesetting("bbox", self.bbox) self.settings:savesetting("globalzoom", self.globalzoom) self.settings:savesetting("globalzoommode", self.globalzoommode) + self.settings:savesetting("highlight", self.highlight) self.settings:close() end @@ -997,6 +1001,11 @@ function UniReader:addAllCommands() function(unireader) unireader:screenRotate("anticlockwise") end) + self.commands:add(KEY_N, nil, "N", + "start highlight mode", + function(unireader) + unireader:startHighLightMode() + end) self.commands:add(KEY_HOME,MOD_SHIFT_OR_ALT,"Home", "exit application", function(unireader) From 7c81f60a58c0239b7035bad48c7147d23e766c24 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 22 Mar 2012 23:12:57 +0800 Subject: [PATCH 016/183] rewrite highlight again :( finished the cursor part --- djvu.c | 103 ++++----- djvureader.lua | 560 +++++++++++++++++++++++++++++++++---------------- inputbox.lua | 2 +- unireader.lua | 1 + 4 files changed, 415 insertions(+), 251 deletions(-) diff --git a/djvu.c b/djvu.c index 789743483..6bcac2290 100644 --- a/djvu.c +++ b/djvu.c @@ -229,37 +229,22 @@ static int getUsedBBox(lua_State *L) { /* * Return a table like following: * { - * { -- a line entry - * words = { - * {word="This", x0=377, y0=4857, x1=2427, y1=5089}, - * {word="is", x0=377, y0=4857, x1=2427, y1=5089}, - * {word="Word", x0=377, y0=4857, x1=2427, y1=5089}, - * {word="List", x0=377, y0=4857, x1=2427, y1=5089}, - * }, + * -- 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 - * words = { - * {word="This", x0=377, y0=4857, x1=2427, y1=5089}, - * {word="is", 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, * }, * } - * - * 5 words in two lines - * { - * 1 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, - * 2 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, - * 3 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, - * 4 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, - * 5 = {word="This", x0=377, y0=4857, x1=2427, y1=5089}, - * lines = { - * 1 = {last = 2, x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089}, - * 2 = {last = 5, x0 = 377, y0 = 4857, x1 = 2427, y1 = 5089}, - * } - * } */ static int getPageText(lua_State *L) { DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); @@ -280,16 +265,10 @@ static int getPageText(lua_State *L) { sexp = miniexp_cdr(sexp); /* get number of lines in a page */ nr_line = miniexp_length(sexp); - /* the outer table */ + /* table that contains all the lines */ lua_newtable(L); - /* create lines subtable */ - lua_pushstring(L, "lines"); - lua_newtable(L); - lua_settable(L, -3); - counter_l = 1; - counter_w = 1; for(i = 1; i <= nr_line; i++) { /* retrive one line entry */ se_line = miniexp_nth(i, sexp); @@ -298,7 +277,30 @@ static int getPageText(lua_State *L) { 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); @@ -334,44 +336,11 @@ static int getPageText(lua_State *L) { lua_pushstring(L, word); lua_settable(L, -3); - /* set word entry to outer table */ + /* set word entry to line subtable */ lua_settable(L, -3); } /* end of for (j) */ - /* get lines table from outer table */ - lua_pushstring(L, "lines"); - lua_getfield(L, -2, "lines"); - - /* subtable that contains info for 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); - - lua_pushstring(L, "last"); - lua_pushnumber(L, counter_w-1); - lua_settable(L, -3); - - /* set line entry to lines subtable */ - lua_settable(L, -3); - - /* set lines subtable back to outer table */ + /* set line entry to page text table */ lua_settable(L, -3); } /* end of for (i) */ diff --git a/djvureader.lua b/djvureader.lua index 770b38922..9081c1467 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -23,117 +23,192 @@ function DJVUReader:_isWordInScreenRange(w) end function DJVUReader:toggleTextHighLight(word_list) - self:_toggleTextHighLightByEnds(word_list, 1, #word_list) + self:_toggleTextHighLight(word_list, 1, 1, + #word_list, #(word_list[#word_list])) end -function DJVUReader:_toggleTextHighLightByEnds(t, end1, end2) - if end1 > end2 then - end1, end2 = end2, end1 +function DJVUReader:_toggleTextHighLight(t, l0, w0, l1, w1) + print("haha", l0, w0, l1, w1) + -- make sure (l0, w0) is smaller than (l1, w1) + if l0 > l1 then + l0, l1 = l1, l0 + w0, w1 = w1, w0 + elseif l0 == l1 and w0 > w1 then + w0, w1 = w1, w0 end - for i=end1, end2, 1 do - if self:_isWordInScreenRange(t[i]) then - self:_toggleWordHighLight(t[i]) + if l0 == l1 then + -- in the same line + for i=w0, w1, 1 do + if self:_isWordInScreenRange(t[l0][i]) then + self:_toggleWordHighLight(t, l0, i) + end end - end + else + -- highlight word in first line as special case + for i=w0, #(t[l0]), 1 do + if self:_isWordInScreenRange(t[l0][i]) then + self:_toggleWordHighLight(t, l0, i) + end + end + + for i=l0+1, l1-1, 1 do + for j=1, #t[i], 1 do + if self:_isWordInScreenRange(t[i][j]) then + self:_toggleWordHighLight(t, i, j) + end + end + end + + -- highlight word in last line as special case + for i=1, w1, 1 do + if self:_isWordInScreenRange(t[l1][i]) then + self:_toggleWordHighLight(t, l1, i) + end + end + end -- EOF if l0==l1 end -function DJVUReader:_toggleWordHighLight(w) - local width = (w.x1-w.x0)*self.globalzoom - local height = (w.y1-w.y0)*self.globalzoom +function DJVUReader:_toggleWordHighLight(t, l, w) + local width = (t[l][w].x1 - t[l][w].x0) * self.globalzoom + local height = (t[l].y1 - t[l].y0) * self.globalzoom fb.bb:invertRect( - w.x0*self.globalzoom-width*0.05, - self.offset_y+self.cur_full_height-(w.y1*self.globalzoom)-height*0.05, - width*1.1, - height*1.1) + t[l][w].x0*self.globalzoom-width*0.05, + self.offset_y+self.cur_full_height-(t[l].y1*self.globalzoom)-height*0.05, + width*1.1, height*1.1) end +--function DJVUReader:_toggleWordHighLight(w) + --local width = (w.x1-w.x0)*self.globalzoom + --local height = (w.y1-w.y0)*self.globalzoom + --fb.bb:invertRect( + --w.x0*self.globalzoom-width*0.05, + --self.offset_y+self.cur_full_height-(w.y1*self.globalzoom)-height*0.05, + --width*1.1, + --height*1.1) +--end + -- remember to clear cursor before calling this -function DJVUReader:drawCursorAfterWord(w) - self.cursor:setHeight((w.y1 - w.y0) * self.globalzoom) - self.cursor:moveTo(w.x1 * self.globalzoom, - self.offset_y + self.cur_full_height - (w.y1 * self.globalzoom)) +function DJVUReader:drawCursorAfterWord(t, l, w) + self.cursor:setHeight((t[l].y1 - t[l].y0) * self.globalzoom) + self.cursor:moveTo(t[l][w].x1 * self.globalzoom, + self.offset_y + self.cur_full_height + - (t[l].y1 * self.globalzoom)) + self.cursor:draw() +end + +function DJVUReader:drawCursorBeforeWord(t, l, w) + self.cursor:setHeight((t[l].y1 - t[l].y0) + * self.globalzoom) + self.cursor:moveTo( + t[l][w].x0*self.globalzoom - self.cursor.w, + self.offset_y + self.cur_full_height + - t[l].y1 * self.globalzoom) self.cursor:draw() end function DJVUReader:startHighLightMode() local t = self.doc:getPageText(self.pageno) - local function _getLineByWord(t, cur_w) - for k,l in ipairs(t.lines) do - if l.last >= cur_w then - return k + local function _findFirstWordInView(t) + -- @TODO maybe we can just check line by line here 22.03 2012 (houqp) + for i=1, #t, 1 do + for j=1, #t[i], 1 do + if self:_isWordInScreenRange(t[i][j]) then + return i, j + end end end + + return nil end - local function _findFirstWordInView(t) - for k,v in ipairs(t) do - if self:_isWordInScreenRange(v) then - return k + local function _prevWord(t, cur_l, cur_w) + if cur_l == 1 then + if cur_w == 1 then + -- already the first word + return 1, 1 + else + -- in first line, but not first word + return cur_l, cur_w -1 end end - return nil - end - local function _wordInNextLine(t, cur_w) - local cur_l = _getLineByWord(t, cur_w) - if cur_l == #t.lines then - -- already in last line, return the last word - return t.lines[cur_l].last + if cur_w <= 1 then + -- first word in current line, goto previous line + return cur_l - 1, #t[cur_l-1] else - local next_l_start = t.lines[cur_l].last + 1 - local cur_l_start = 1 - if cur_l ~= 1 then - cur_l_start = t.lines[cur_l-1].last + 1 - end + return cur_l, cur_w - 1 + end + end - cur_w = next_l_start + (cur_w - cur_l_start) - if cur_w > t.lines[cur_l+1].last then - cur_w = t.lines[cur_l+1].last + local function _nextWord(t, cur_l, cur_w) + if cur_l == #t then + if cur_w == #(t[cur_l]) then + -- already the last word + return cur_l, cur_w + else + -- in last line, but not last word + return cur_l, cur_w + 1 end - return cur_w + end + + if cur_w < #t[cur_l] then + return cur_l, cur_w + 1 + else + -- last word in current line, move to next line + return cur_l + 1, 1 end end - local function _wordInPrevLine(t, cur_w) - local cur_l = _getLineByWord(t, cur_w) - if cur_l == 1 then - -- already in first line, return 0 - return 0 + local function _wordInNextLine(t, cur_l, cur_w) + if cur_l == #t then + -- already in last line, return the last word + return cur_l, #(t[cur_l]) else - local prev_l_start = 1 - if cur_l > 2 then - -- previous line is not the first line - prev_l_start = t.lines[cur_l-2].last + 1 - end - local cur_l_start = t.lines[cur_l-1].last + 1 + return cur_l + 1, math.min(cur_w, #t[cur_l+1]) + end + end - cur_w = prev_l_start + (cur_w - cur_l_start) - if cur_w > t.lines[cur_l-1].last then - cur_w = t.lines[cur_l-1].last - end - return cur_w + local function _wordInPrevLine(t, cur_l, cur_w) + if cur_l == 1 then + -- already in first line, return the first word + return 1, 1 + else + return cur_l - 1, math.min(cur_w, #t[cur_l-1]) end end + local function _isMovingForward(l, w) + return l.cur > l.start or (l.cur == l.start and w.cur >= w.start) + end + + local function _isMovingBackward(l, w) + return not _isMovingForward(l, w) + end - local start_w = _findFirstWordInView(t) - if not start_w then + local l = {} + local w = {} + + l.start, w.start = _findFirstWordInView(t) + --local l.start, w.start = _findFirstWordInView(t) + if not l.start then print("# no text in current view!") return end - local cur_w = start_w - local new_w = 1 + l.cur, w.cur = l.start, w.start + l.new, w.new = l.cur, w.cur local is_hightlight_mode = false + local is_meet_start = false + local is_meet_end = false local running = true - + self.cursor = Cursor:new { - x_pos = t[cur_w].x1*self.globalzoom, + x_pos = t[l.cur][w.cur].x1*self.globalzoom, y_pos = self.offset_y + (self.cur_full_height - - (t[cur_w].y1 * self.globalzoom)), - h = (t[cur_w].y1 - t[cur_w].y0) * self.globalzoom, + - (t[l.cur][w.cur].y1 * self.globalzoom)), + h = (t[l.cur][w.cur].y1 - t[l.cur][w.cur].y0) * self.globalzoom, line_width_factor = 4, } self.cursor:draw() @@ -144,169 +219,288 @@ function DJVUReader:startHighLightMode() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then if ev.code == KEY_FW_LEFT then - if cur_w >= 1 then + local is_next_view = false + if w.cur == 1 then + w.cur = 0 + w.new = 0 + else + if w.cur == 0 then + -- already at the left end of current line, + -- goto previous line (_prevWord does not understand + -- zero w.cur) + w.cur = 1 + end + l.new, w.new = _prevWord(t, l.cur, w.cur) + end + + if w.new ~= 0 and + not self:_isWordInScreenRange(t[l.new][w.new]) then + -- word is in previous view + local pageno = self:prevView() + self:goto(pageno) + is_next_view = true + else + -- no need to goto next view, clear previous cursor manually + self.cursor:clear() + end + + -- update cursor + if w.cur == 0 then + -- meet line left end, must be handled as special case + self:drawCursorBeforeWord(t, l.cur, 1) + else + self:drawCursorAfterWord(t, l.new, w.new) + end + elseif ev.code == KEY_FW_RIGHT then + local is_next_view = false + if w.cur == 0 then + w.cur = 1 + w.new = 1 + else + l.new, w.new = _nextWord(t, l.cur, w.cur) + if w.new == 1 then + -- Must be come from the right end of previous line, so + -- goto the left end of current line. + w.cur = 0 + w.new = 0 + end + end + + if w.new ~= 0 and + not self:_isWordInScreenRange(t[l.new][w.new]) then + local pageno = self:nextView() + self:goto(pageno) + is_next_view = true + else + self.cursor:clear() + end + + if w.cur == 0 then + -- meet line left end, must be handled as special case + self:drawCursorBeforeWord(t, l.new, 1) + else + self:drawCursorAfterWord(t, l.new, w.new) + end + elseif ev.code == KEY_FW_UP then + local is_next_view = false + if w.cur == 0 then + -- goto left end of last line + l.new = math.max(l.cur - 1, 1) + elseif l.cur == 1 and w.cur == 1 then + -- already first word, to the left end of first line + w.new = 0 + else + l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) + end + + if w.new ~= 0 and + not self:_isWordInScreenRange(t[l.new][w.new]) + or w.new == 0 and not self:_isWordInScreenRange(t[l.new][1]) then + -- goto next view of current page + local pageno = self:prevView() + self:goto(pageno) + is_next_view = true + else + self.cursor:clear() + end + + if w.new == 0 then + self:drawCursorBeforeWord(t, l.new, 1) + else + self:drawCursorAfterWord(t, l.new, w.new) + end + elseif ev.code == KEY_FW_DOWN then + local is_next_view = false + if w.cur == 0 then + -- on the left end of current line, + -- goto left end of next line + l.new = math.min(l.cur + 1, #t) + else + l.new, w.new = _wordInNextLine(t, l.cur, w.cur) + end + + if w.cur ~= 0 and + not self:_isWordInScreenRange(t[l.new][w.new]) + or w.cur == 0 and not self:_isWordInScreenRange(t[l.new][1]) then + -- goto next view of current page + local pageno = self:nextView() + self:goto(pageno) + is_next_view = true + else + self.cursor:clear() + end + + if w.cur == 0 then + self:drawCursorBeforeWord(t, l.new, 1) + else + self:drawCursorAfterWord(t, l.new, w.new) + end + elseif ev.code == KEY_FW_PRESS then + if w.cur == 0 then + w.cur = 1 + end + l.start, w.start = l.cur, w.cur + running = false + elseif ev.code == KEY_BACK then + running = false + return + end -- EOF if key event + l.cur, w.cur = l.new, w.new + fb:refresh(0) + end + end -- EOF while + + + print("!!!!cccccccc", l.start, w.start) + running = true + + -- in highlight mode + while running do + local ev = input.waitForEvent() + ev.code = adjustKeyEvents(ev) + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + if ev.code == KEY_FW_LEFT then + is_meet_end = false + if not is_meet_start then local is_next_view = false - new_w = cur_w - 1 + l.new, w.new = _prevWord(t, l.cur, w.cur) - if new_w ~= 0 and - not self:_isWordInScreenRange(t[new_w]) then + if l.new == l.cur and w.new == w.cur then + is_meet_start = true + end + + if l.new ~= 0 and w.new ~= 0 and + not self:_isWordInScreenRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() self:goto(pageno) is_next_view = true - else - self.cursor:clear() - end - - -- update cursor - if new_w == 0 then - -- meet top end, must be handled as special case - self.cursor:setHeight((t[1].y1 - t[1].y0) - * self.globalzoom) - self.cursor:moveTo( - t[1].x0*self.globalzoom - self.cursor.w, - self.offset_y + self.cur_full_height - - t[1].y1 * self.globalzoom) - self.cursor:draw() - else - self:drawCursorAfterWord(t[new_w]) end if is_hightlight_mode then -- update highlight - if new_w ~= 0 and is_next_view then - self:_toggleTextHighLightByEnds(t, start_w, new_w) + if w.new ~= 0 and is_next_view then + self:_toggleTextHighLight(t, l.start, w.start, + l.new, w.new) else - self:_toggleWordHighLight(t[new_w+1]) + self:_toggleWordHighLight(t, l.cur, w.cur) end end end elseif ev.code == KEY_FW_RIGHT then - -- only highlight word in current page - if cur_w < #t then + is_meet_start = false + if not is_meet_end then local is_next_view = false - new_w = cur_w + 1 + l.new, w.new = _nextWord(t, l.cur, w.cur) + if l.new == l.cur and w.new == w.cur then + is_meet_end = true + end - if not self:_isWordInScreenRange(t[new_w]) then + if not self:_isWordInScreenRange(t[l.new][w.new]) then local pageno = self:nextView() self:goto(pageno) is_next_view = true - else - self.cursor:clear() end - -- update cursor - self:drawCursorAfterWord(t[new_w]) - - if is_hightlight_mode then - -- update highlight - if is_next_view then - self:_toggleTextHighLightByEnds(t, start_w, new_w) - else - self:_toggleWordHighLight(t[new_w]) - end + -- update highlight + if is_next_view then + self:_toggleTextHighLight(t, l.start, w.start, + l.new, w.new) + else + self:_toggleWordHighLight(t, l.new, w.new) end - end + end -- EOF if is not is_meet_end elseif ev.code == KEY_FW_UP then + is_meet_end = false + if not is_meet_start then local is_next_view = false - new_w = _wordInPrevLine(t, cur_w) + l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) - if new_w ~= 0 and - not self:_isWordInScreenRange(t[new_w]) then + if l.new ~= 0 and w.new ~= 0 and + not self:_isWordInScreenRange(t[l.new][w.new]) then -- goto next view of current page local pageno = self:prevView() self:goto(pageno) is_next_view = true - else - -- no need to jump to next view, clear previous cursor - self.cursor:clear() end - if new_w == 0 then - -- meet top left end, must be handled as special case - self.cursor:setHeight((t[1].y1 - t[1].y0) - * self.globalzoom) - self.cursor:moveTo( - t[1].x0*self.globalzoom - self.cursor.w, - self.offset_y + self.cur_full_height - - t[1].y1 * self.globalzoom) - self.cursor:draw() + -- update highlight + if l.new ~=0 and w.new ~= 0 and is_next_view then + -- word is in previous view + self:_toggleTextHighLight(t, l.start, w.start, + l.new, w.new) else - self:drawCursorAfterWord(t[new_w]) - end - - if is_hightlight_mode then - -- update highlight - if new_w ~= 0 and is_next_view then - -- word is in previous view - self:_toggleTextHighLightByEnds(t, start_w, new_w) + local tmp_l, tmp_w + if _isMovingForward(l, w) then + tmp_l, tmp_w = _nextWord(t, l.new, w.new) + self:_toggleTextHighLight(t, tmp_l, tmp_w, + l.cur, w.cur) else - for i=new_w+1, cur_w, 1 do - self:_toggleWordHighLight(t[i]) - end - end - end + l.new, w.new = _nextWord(t, l.new, w.new) + self:_toggleTextHighLight(t, l.new, w.new, + l.cur, w.cur) + l.new, w.new = _prevWord(t, l.new, w.new) + end -- EOF if is moving forward + end -- EOF if is previous view + end -- EOF if is not is_meet_start elseif ev.code == KEY_FW_DOWN then local is_next_view = false - new_w = _wordInNextLine(t, cur_w) + l.new, w.new = _wordInNextLine(t, l.cur, w.cur) - if not self:_isWordInScreenRange(t[new_w]) then + if not self:_isWordInScreenRange(t[l.new][w.new]) then -- goto next view of current page local pageno = self:nextView() self:goto(pageno) is_next_view = true - else - -- no need to jump to next view, clear previous cursor - self.cursor:clear() end - -- update cursor - self:drawCursorAfterWord(t[new_w]) - - if is_hightlight_mode then - -- update highlight - if is_next_view then - -- redraw from start because of page refresh - self:_toggleTextHighLightByEnds(t, start_w, new_w) + -- update highlight + if is_next_view then + -- redraw from start because of page refresh + self:_toggleTextHighLight(t, l.start, w.start, + l.new, w.new) + else + -- word in next is in current view, just highlight it + if _isMovingForward(l, w) then + l.cur, w.cur = _nextWord(t, l.cur, w.cur) + self:_toggleTextHighLight(t, l.cur, w.cur, + l.new, w.new) else - -- word in next is in current view, just highlight it - for i=cur_w+1, new_w, 1 do - self:_toggleWordHighLight(t[i]) - end - end - end + l.cur, w.cur = _nextWord(t, l.cur, w.cur) + self:_toggleTextHighLight(t, l.cur, w.cur, + l.new, w.new) + end -- EOF if moving forward + end -- EOF if next view elseif ev.code == KEY_FW_PRESS then - if not is_hightlight_mode then - is_hightlight_mode = true - start_w = cur_w - else -- pressed in highlight mode, record selected text - local first, last = 0 - if start_w < cur_w then - first = start_w + 1 - last = cur_w - else - first = cur_w + 1 - last = start_w - end - --self:_toggleTextHighLightByEnds(t, first, last) - - local hl_item = {} - for i=first,last,1 do - table.insert(hl_item, t[i]) - end - if not self.highlight[self.pageno] then - self.highlight[self.pageno] = {} - end - table.insert(self.highlight[self.pageno], hl_item) + local first, last = 0 + if w.start < w.cur then + first = w.start + 1 + last = w.cur + else + first = w.cur + 1 + last = w.start + end + --self:_toggleTextHighLightByEnds(t, first, last) - is_hightlight_mode = false - running = false - self.cursor:clear() + local hl_item = {} + for i=first,last,1 do + table.insert(hl_item, t[i]) + end + if not self.highlight[self.pageno] then + self.highlight[self.pageno] = {} end - end -- EOF if keyevent - cur_w = new_w + table.insert(self.highlight[self.pageno], hl_item) + + is_hightlight_mode = false + running = false + self.cursor:clear() + elseif ev.code == KEY_BACK then + running = false + end -- EOF if key event + l.cur, w.cur = l.new, w.new fb:refresh(0) - end -- EOF while - end + end + end -- EOF while + end diff --git a/inputbox.lua b/inputbox.lua index e30e88507..abbaeb687 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -43,7 +43,7 @@ function InputBox:addChar(char) -- draw new text local cur_index = (self.cursor.x_pos + 3 - self.input_start_x) / self.fwidth - self.input_string = self.input_string:sub(0,cur_index)..char.. + 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 diff --git a/unireader.lua b/unireader.lua index be2d97c33..2141e16d6 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1005,6 +1005,7 @@ function UniReader:addAllCommands() "start highlight mode", function(unireader) unireader:startHighLightMode() + unireader:goto(unireader.pageno) end) self.commands:add(KEY_HOME,MOD_SHIFT_OR_ALT,"Home", "exit application", From 81a1f3d366e9c4690fb0cd778195e2790fae359b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 23 Mar 2012 15:51:48 +0800 Subject: [PATCH 017/183] demo of text highlight * text selection * highlight save and restore --- djvureader.lua | 391 ++++++++++++++++++++++++++----------------------- unireader.lua | 45 +++--- 2 files changed, 235 insertions(+), 201 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 9081c1467..81307bb75 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -23,12 +23,84 @@ function DJVUReader:_isWordInScreenRange(w) end function DJVUReader:toggleTextHighLight(word_list) - self:_toggleTextHighLight(word_list, 1, 1, - #word_list, #(word_list[#word_list])) + for _,text_item in ipairs(word_list) do + for _,line_item in ipairs(text_item) do + -- make sure that line is in screen range + if self:_isWordInScreenRange(line_item) then + local x, y, w, h = self:_rectCoordTransform( + line_item.x0, line_item.y0, + line_item.x1, line_item.y1) + -- slightly enlarge the highlight height + -- for better viewing experience + x = x + y = y - h * 0.1 + w = w + h = h * 1.2 + + fb.bb:invertRect(x, y, w, h) + end -- EOF if isWordInScreenRange + end -- EOF for line_item + end -- EOF for text_item +end + +function DJVUReader:_wordIterFromRange(t, l0, w0, l1, w1) + local i = l0 + local j = w0 - 1 + return function() + if i <= l1 then + -- if in line range, loop through lines + if i == l1 then + -- in last line + if j < w1 then + j = j + 1 + else + -- out of range return nil + return nil, nil + end + else + if j < #t[i] then + j = j + 1 + else + -- goto next line + i = i + 1 + j = 1 + end + end + return i, j + end + end -- EOF closure +end + +---------------------------------------------------- +-- Given coordinates of four conners and return +-- coordinate of upper left conner with with and height +-- +-- In djvulibre library, some coordinates starts from +-- down left conner, i.e. y is upside down. This method +-- only transform these coordinates. +---------------------------------------------------- +function DJVUReader:_rectCoordTransform(x0, y0, x1, y1) + return + x0 * self.globalzoom, + self.offset_y + self.cur_full_height - (y1 * self.globalzoom), + (x1 - x0) * self.globalzoom, + (y1 - y0) * self.globalzoom +end + +function DJVUReader:_toggleWordHighLight(t, l, w) + x, y, w, h = self:_rectCoordTransform(t[l][w].x0, t[l].y0, + t[l][w].x1, t[l].y1) + -- slightly enlarge the highlight range for better viewing experience + x = x - w * 0.05 + y = y - h * 0.05 + w = w * 1.1 + h = h * 1.1 + + fb.bb:invertRect(x, y, w, h) end function DJVUReader:_toggleTextHighLight(t, l0, w0, l1, w1) - print("haha", l0, w0, l1, w1) + --print("# toggle range", l0, w0, l1, w1) -- make sure (l0, w0) is smaller than (l1, w1) if l0 > l1 then l0, l1 = l1, l0 @@ -37,57 +109,13 @@ function DJVUReader:_toggleTextHighLight(t, l0, w0, l1, w1) w0, w1 = w1, w0 end - if l0 == l1 then - -- in the same line - for i=w0, w1, 1 do - if self:_isWordInScreenRange(t[l0][i]) then - self:_toggleWordHighLight(t, l0, i) - end - end - else - -- highlight word in first line as special case - for i=w0, #(t[l0]), 1 do - if self:_isWordInScreenRange(t[l0][i]) then - self:_toggleWordHighLight(t, l0, i) - end - end - - for i=l0+1, l1-1, 1 do - for j=1, #t[i], 1 do - if self:_isWordInScreenRange(t[i][j]) then - self:_toggleWordHighLight(t, i, j) - end - end + for _l, _w in self:_wordIterFromRange(t, l0, w0, l1, w1) do + if self:_isWordInScreenRange(t[_l][_w]) then + self:_toggleWordHighLight(t, _l, _w) end - - -- highlight word in last line as special case - for i=1, w1, 1 do - if self:_isWordInScreenRange(t[l1][i]) then - self:_toggleWordHighLight(t, l1, i) - end - end - end -- EOF if l0==l1 -end - -function DJVUReader:_toggleWordHighLight(t, l, w) - local width = (t[l][w].x1 - t[l][w].x0) * self.globalzoom - local height = (t[l].y1 - t[l].y0) * self.globalzoom - fb.bb:invertRect( - t[l][w].x0*self.globalzoom-width*0.05, - self.offset_y+self.cur_full_height-(t[l].y1*self.globalzoom)-height*0.05, - width*1.1, height*1.1) + end end ---function DJVUReader:_toggleWordHighLight(w) - --local width = (w.x1-w.x0)*self.globalzoom - --local height = (w.y1-w.y0)*self.globalzoom - --fb.bb:invertRect( - --w.x0*self.globalzoom-width*0.05, - --self.offset_y+self.cur_full_height-(w.y1*self.globalzoom)-height*0.05, - --width*1.1, - --height*1.1) ---end - -- remember to clear cursor before calling this function DJVUReader:drawCursorAfterWord(t, l, w) self.cursor:setHeight((t[l].y1 - t[l].y0) * self.globalzoom) @@ -111,12 +139,9 @@ function DJVUReader:startHighLightMode() local t = self.doc:getPageText(self.pageno) local function _findFirstWordInView(t) - -- @TODO maybe we can just check line by line here 22.03 2012 (houqp) for i=1, #t, 1 do - for j=1, #t[i], 1 do - if self:_isWordInScreenRange(t[i][j]) then - return i, j - end + if self:_isWordInScreenRange(t[i][1]) then + return i, 1 end end @@ -180,18 +205,13 @@ function DJVUReader:startHighLightMode() end local function _isMovingForward(l, w) - return l.cur > l.start or (l.cur == l.start and w.cur >= w.start) - end - - local function _isMovingBackward(l, w) - return not _isMovingForward(l, w) + return l.cur > l.start or (l.cur == l.start and w.cur > w.start) end local l = {} local w = {} l.start, w.start = _findFirstWordInView(t) - --local l.start, w.start = _findFirstWordInView(t) if not l.start then print("# no text in current view!") return @@ -199,7 +219,6 @@ function DJVUReader:startHighLightMode() l.cur, w.cur = l.start, w.start l.new, w.new = l.cur, w.cur - local is_hightlight_mode = false local is_meet_start = false local is_meet_end = false local running = true @@ -214,6 +233,7 @@ function DJVUReader:startHighLightMode() self.cursor:draw() fb:refresh(0) + -- first use cursor to place start pos for highlight while running do local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) @@ -338,9 +358,12 @@ function DJVUReader:startHighLightMode() elseif ev.code == KEY_FW_PRESS then if w.cur == 0 then w.cur = 1 + l.cur, w.cur = _prevWord(t, l.cur, w.cur) end + l.new, w.new = l.cur, w.cur l.start, w.start = l.cur, w.cur running = false + self.cursor:clear() elseif ev.code == KEY_BACK then running = false return @@ -349,12 +372,74 @@ function DJVUReader:startHighLightMode() fb:refresh(0) end end -- EOF while + --print("start", l.cur, w.cur, l.start, w.start) + -- two helper functions for highlight + local function _togglePrevWordHighLight(t, l, w) + l.new, w.new = _prevWord(t, l.cur, w.cur) - print("!!!!cccccccc", l.start, w.start) - running = true + if l.cur == 1 and w.cur == 1 then + is_meet_start = true + -- left end of first line must be handled as special case + w.new = 0 + end + + if w.new ~= 0 and + not self:_isWordInScreenRange(t[l.new][w.new]) then + -- word is in previous view + local pageno = self:prevView() + self:goto(pageno) + + local l0 = l.start + local w0 = w.start + local l1 = l.cur + local w1 = w.cur + if _isMovingForward(l, w) then + l0, w0 = _nextWord(t, l0, w0) + l1, w1 = l.new, w.new + end + self:_toggleTextHighLight(t, l0, w0, + l1, w1) + else + self:_toggleWordHighLight(t, l.cur, w.cur) + end + + l.cur, w.cur = l.new, w.new + return l, w, (is_meet_start or false) + end + + local function _toggleNextWordHighLight(t, l, w) + if w.cur == 0 then + w.new = 1 + else + l.new, w.new = _nextWord(t, l.cur, w.cur) + end + if l.new == #t and w.new == #t[#t] then + is_meet_end = true + end + + if not self:_isWordInScreenRange(t[l.new][w.new]) then + local pageno = self:nextView() + self:goto(pageno) - -- in highlight mode + local tmp_l = l.start + local tmp_w = w.start + if _isMovingForward(l, w) then + tmp_l, tmp_w = _nextWord(t, tmp_l, tmp_w) + end + self:_toggleTextHighLight(t, tmp_l, tmp_w, + l.new, w.new) + else + self:_toggleWordHighLight(t, l.new, w.new) + end + + l.cur, w.cur = l.new, w.new + return l, w, (is_meet_end or false) + end + + + -- go into highlight mode + running = true while running do local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) @@ -362,145 +447,91 @@ function DJVUReader:startHighLightMode() if ev.code == KEY_FW_LEFT then is_meet_end = false if not is_meet_start then - local is_next_view = false - l.new, w.new = _prevWord(t, l.cur, w.cur) - - if l.new == l.cur and w.new == w.cur then - is_meet_start = true - end - - if l.new ~= 0 and w.new ~= 0 and - not self:_isWordInScreenRange(t[l.new][w.new]) then - -- word is in previous view - local pageno = self:prevView() - self:goto(pageno) - is_next_view = true - end - - if is_hightlight_mode then - -- update highlight - if w.new ~= 0 and is_next_view then - self:_toggleTextHighLight(t, l.start, w.start, - l.new, w.new) - else - self:_toggleWordHighLight(t, l.cur, w.cur) - end - end + l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) end elseif ev.code == KEY_FW_RIGHT then is_meet_start = false if not is_meet_end then - local is_next_view = false - l.new, w.new = _nextWord(t, l.cur, w.cur) - if l.new == l.cur and w.new == w.cur then - is_meet_end = true - end - - if not self:_isWordInScreenRange(t[l.new][w.new]) then - local pageno = self:nextView() - self:goto(pageno) - is_next_view = true - end - - -- update highlight - if is_next_view then - self:_toggleTextHighLight(t, l.start, w.start, - l.new, w.new) - else - self:_toggleWordHighLight(t, l.new, w.new) - end + l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) end -- EOF if is not is_meet_end elseif ev.code == KEY_FW_UP then - is_meet_end = false - if not is_meet_start then - local is_next_view = false - l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) - - if l.new ~= 0 and w.new ~= 0 and - not self:_isWordInScreenRange(t[l.new][w.new]) then - -- goto next view of current page - local pageno = self:prevView() - self:goto(pageno) - is_next_view = true - end - - -- update highlight - if l.new ~=0 and w.new ~= 0 and is_next_view then - -- word is in previous view - self:_toggleTextHighLight(t, l.start, w.start, - l.new, w.new) - else - local tmp_l, tmp_w - if _isMovingForward(l, w) then - tmp_l, tmp_w = _nextWord(t, l.new, w.new) - self:_toggleTextHighLight(t, tmp_l, tmp_w, - l.cur, w.cur) - else - l.new, w.new = _nextWord(t, l.new, w.new) - self:_toggleTextHighLight(t, l.new, w.new, - l.cur, w.cur) - l.new, w.new = _prevWord(t, l.new, w.new) - end -- EOF if is moving forward - end -- EOF if is previous view - end -- EOF if is not is_meet_start - elseif ev.code == KEY_FW_DOWN then - local is_next_view = false - l.new, w.new = _wordInNextLine(t, l.cur, w.cur) - - if not self:_isWordInScreenRange(t[l.new][w.new]) then - -- goto next view of current page - local pageno = self:nextView() - self:goto(pageno) - is_next_view = true + if l.cur == 1 then + -- handle left end of first line as special case + tmp_l = 1 + tmp_w = 0 + else + tmp_l, tmp_w = _wordInPrevLine(t, l.cur, w.cur) end - - -- update highlight - if is_next_view then - -- redraw from start because of page refresh - self:_toggleTextHighLight(t, l.start, w.start, - l.new, w.new) + while not (tmp_l == l.cur and tmp_w == w.cur) do + l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) + end + elseif ev.code == KEY_FW_DOWN then + if w.cur == 0 then + -- handle left end of first line as special case + tmp_l = math.min(tmp_l + 1, #t) + tmp_w = 1 else - -- word in next is in current view, just highlight it - if _isMovingForward(l, w) then - l.cur, w.cur = _nextWord(t, l.cur, w.cur) - self:_toggleTextHighLight(t, l.cur, w.cur, - l.new, w.new) - else - l.cur, w.cur = _nextWord(t, l.cur, w.cur) - self:_toggleTextHighLight(t, l.cur, w.cur, - l.new, w.new) - end -- EOF if moving forward - end -- EOF if next view + tmp_l, tmp_w = _wordInNextLine(t, l.new, w.new) + end + while not (tmp_l == l.cur and tmp_w == w.cur) do + l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) + end elseif ev.code == KEY_FW_PRESS then - local first, last = 0 - if w.start < w.cur then - first = w.start + 1 - last = w.cur + local l0, w0, l1, w1 + + -- find start and end of highlight text + if _isMovingForward(l, w) then + l0, w0 = _nextWord(t, l.start, w.start) + l1, w1 = l.cur, w.cur else - first = w.cur + 1 - last = w.start + l0, w0 = _nextWord(t, l.cur, w.cur) + l1, w1 = l.start, w.start end - --self:_toggleTextHighLightByEnds(t, first, last) + -- remove selection area + self:_toggleTextHighLight(t, l0, w0, l1, w1) + -- put text into highlight table of current page local hl_item = {} - for i=first,last,1 do - table.insert(hl_item, t[i]) + local s = "" + local prev_l = l0 + local prev_w = w0 + local l_item = { + x0 = t[l0][w0].x0, + y0 = t[l0].y0, + y1 = t[l0].y1, + } + for _l,_w in self:_wordIterFromRange(t, l0, w0, l1, w1) do + local word_item = t[_l][_w] + if _l > prev_l then + -- in next line, add previous line to highlight item + l_item.x1 = t[prev_l][prev_w].x1 + table.insert(hl_item, l_item) + -- re initialize l_item for new line + l_item = { + x0 = word_item.x0, + y0 = t[_l].y0, + y1 = t[_l].y1, + } + end + s = s .. word_item.word .. " " + prev_l, prev_w = _l, _w end + -- insert last line of text in line item + l_item.x1 = t[prev_l][prev_w].x1 + table.insert(hl_item, l_item) + hl_item.text = s + if not self.highlight[self.pageno] then self.highlight[self.pageno] = {} end table.insert(self.highlight[self.pageno], hl_item) - is_hightlight_mode = false running = false - self.cursor:clear() elseif ev.code == KEY_BACK then running = false end -- EOF if key event - l.cur, w.cur = l.new, w.new fb:refresh(0) end end -- EOF while - end diff --git a/unireader.lua b/unireader.lua index 2141e16d6..f607e99ef 100644 --- a/unireader.lua +++ b/unireader.lua @@ -89,14 +89,17 @@ function UniReader:new(o) return o end ---[[ - For a new specific reader, - you must always overwrite following two methods: - - * self:open() - - overwrite other methods if needed. ---]] +---------------------------------------------------- +-- !!!!!!!!!!!!!!!!!!!!!!!!! +-- +-- For a new specific reader, +-- you must always overwrite following two methods: +-- +-- * self:open() +-- * self:init() +-- +-- overwrite other methods if needed. +---------------------------------------------------- function UniReader:init() end @@ -106,6 +109,17 @@ function UniReader:open(filename, password) return false end +---------------------------------------------------- +-- You need to overwrite following two methods if your +-- reader supports highlight feature. +---------------------------------------------------- +function UniReader:highLightText() + return +end + +function UniReader:toggleTextHighLight(word_list) + return +end --[ following are default methods ]-- @@ -485,11 +499,9 @@ function UniReader:show(no) "width:"..width..", height:"..height) fb.bb:blitFrom(bb, dest_x, dest_y, offset_x, offset_y, width, height) - -- add highlights + -- render highlights to page if self.highlight[no] then - for k,v in ipairs(self.highlight[no]) do - self:toggleTextHighLight(v) - end + self:toggleTextHighLight(self.highlight[no]) end if self.rcount == self.rcountmax then @@ -745,15 +757,6 @@ function UniReader:showJumpStack() end end -function UniReader:highLightText() - return -end - - -function UniReader:toggleTextHighLight(word_list) - return -end - function UniReader:showMenu() local ypos = height - 50 local load_percent = (self.pageno / self.doc:getPages()) From e30b88d135d0e4ba3171bcfbbf9bbe9a062751e0 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 23 Mar 2012 16:56:01 +0800 Subject: [PATCH 018/183] add: delete feature in djvu highlight --- djvureader.lua | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 81307bb75..273f423b8 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -231,7 +231,7 @@ function DJVUReader:startHighLightMode() line_width_factor = 4, } self.cursor:draw() - fb:refresh(0) + fb:refresh(1) -- first use cursor to place start pos for highlight while running do @@ -355,6 +355,23 @@ function DJVUReader:startHighLightMode() else self:drawCursorAfterWord(t, l.new, w.new) end + elseif ev.code == KEY_DEL then + if self.highlight[self.pageno] then + for k, text_item in ipairs(self.highlight[self.pageno]) do + for _, line_item in ipairs(text_item) do + if t[l.cur][w.cur].y0 >= line_item.y0 + and t[l.cur][w.cur].y1 <= line_item.y1 + and t[l.cur][w.cur].x0 >= line_item.x0 + and t[l.cur][w.cur].x1 <= line_item.x1 then + self.highlight[self.pageno][k] = nil + end + end -- EOF for line_item + end -- EOF for text_item + end -- EOF if not highlight table + if #self.highlight[self.pageno] == 0 then + self.highlight[self.pageno] = nil + end + return elseif ev.code == KEY_FW_PRESS then if w.cur == 0 then w.cur = 1 @@ -369,7 +386,7 @@ function DJVUReader:startHighLightMode() return end -- EOF if key event l.cur, w.cur = l.new, w.new - fb:refresh(0) + fb:refresh(1) end end -- EOF while --print("start", l.cur, w.cur, l.start, w.start) @@ -530,7 +547,7 @@ function DJVUReader:startHighLightMode() elseif ev.code == KEY_BACK then running = false end -- EOF if key event - fb:refresh(0) + fb:refresh(1) end end -- EOF while end From 35abbc93d468e6be5f94b1e36ecf97510fcc07bb Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 23 Mar 2012 17:20:51 +0800 Subject: [PATCH 019/183] add: configurable highlight drawer * underscore style (default) * marker style --- djvureader.lua | 48 +++++++++++++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 273f423b8..4f06192e0 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -13,6 +13,27 @@ function DJVUReader:open(filename) return ok end + + +-----------[ highlight support ]---------- + +---------------------------------------------------- +-- Given coordinates of four conners and return +-- coordinate of upper left conner with with and height +-- +-- In djvulibre library, some coordinates starts from +-- down left conner, i.e. y is upside down. This method +-- only transform these coordinates. +---------------------------------------------------- +function DJVUReader:_rectCoordTransform(x0, y0, x1, y1) + return + self.offset_x + x0 * self.globalzoom, + self.offset_y + self.cur_full_height - (y1 * self.globalzoom), + (x1 - x0) * self.globalzoom, + (y1 - y0) * self.globalzoom +end + + function DJVUReader:_isWordInScreenRange(w) -- y axel in djvulibre starts from bottom return (w ~= nil) and ( @@ -37,7 +58,16 @@ function DJVUReader:toggleTextHighLight(word_list) w = w h = h * 1.2 - fb.bb:invertRect(x, y, w, h) + self.highlight.drawer = self.highlight.drawer or "underscore" + if self.highlight.drawer == "underscore" then + self.highlight.line_width = self.highlight.line_width or 2 + self.highlight.line_color = self.highlight.line_color or 5 + fb.bb:paintRect(x, y+h-1, w, + self.highlight.line_width, + self.highlight.line_color) + elseif self.highlight.drawer == "marker" then + fb.bb:invertRect(x, y, w, h) + end end -- EOF if isWordInScreenRange end -- EOF for line_item end -- EOF for text_item @@ -71,22 +101,6 @@ function DJVUReader:_wordIterFromRange(t, l0, w0, l1, w1) end -- EOF closure end ----------------------------------------------------- --- Given coordinates of four conners and return --- coordinate of upper left conner with with and height --- --- In djvulibre library, some coordinates starts from --- down left conner, i.e. y is upside down. This method --- only transform these coordinates. ----------------------------------------------------- -function DJVUReader:_rectCoordTransform(x0, y0, x1, y1) - return - x0 * self.globalzoom, - self.offset_y + self.cur_full_height - (y1 * self.globalzoom), - (x1 - x0) * self.globalzoom, - (y1 - y0) * self.globalzoom -end - function DJVUReader:_toggleWordHighLight(t, l, w) x, y, w, h = self:_rectCoordTransform(t[l][w].x0, t[l].y0, t[l][w].x1, t[l].y1) From c756fcbf1063cffb4fcf712538781f10e1b29b67 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 23 Mar 2012 18:28:15 +0800 Subject: [PATCH 020/183] fix: cursor move in zoom in mode only move cursor to word within screen range --- djvureader.lua | 90 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 4f06192e0..b4039145d 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -35,19 +35,30 @@ end function DJVUReader:_isWordInScreenRange(w) - -- y axel in djvulibre starts from bottom - return (w ~= nil) and ( - ( self.cur_full_height-(w.y0*self.globalzoom) <= - -self.offset_y + height ) and - ( self.cur_full_height-(w.y1*self.globalzoom) >= - -self.offset_y )) + return self:_isWordInScreenHeightRange(w) and + self:_isWordInScreenWidthRange(w) +end + +-- y axel in djvulibre starts from bottom +function DJVUReader:_isWordInScreenHeightRange(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 + height) +end + +function DJVUReader:_isWordInScreenWidthRange(w) + return (w ~= nil) and + (w.x0 * self.globalzoom >= -self.offset_x) and + (w.x1 * self.globalzoom <= -self.offset_x + width) end function DJVUReader:toggleTextHighLight(word_list) for _,text_item in ipairs(word_list) do for _,line_item in ipairs(text_item) do -- make sure that line is in screen range - if self:_isWordInScreenRange(line_item) then + if self:_isWordInScreenHeightRange(line_item) then local x, y, w, h = self:_rectCoordTransform( line_item.x0, line_item.y0, line_item.x1, line_item.y1) @@ -68,7 +79,7 @@ function DJVUReader:toggleTextHighLight(word_list) elseif self.highlight.drawer == "marker" then fb.bb:invertRect(x, y, w, h) end - end -- EOF if isWordInScreenRange + end -- EOF if isWordInScreenHeightRange end -- EOF for line_item end -- EOF for text_item end @@ -124,7 +135,7 @@ function DJVUReader:_toggleTextHighLight(t, l0, w0, l1, w1) end for _l, _w in self:_wordIterFromRange(t, l0, w0, l1, w1) do - if self:_isWordInScreenRange(t[_l][_w]) then + if self:_isWordInScreenHeightRange(t[_l][_w]) then self:_toggleWordHighLight(t, _l, _w) end end @@ -133,9 +144,9 @@ end -- remember to clear cursor before calling this function DJVUReader:drawCursorAfterWord(t, l, w) self.cursor:setHeight((t[l].y1 - t[l].y0) * self.globalzoom) - self.cursor:moveTo(t[l][w].x1 * self.globalzoom, - self.offset_y + self.cur_full_height - - (t[l].y1 * self.globalzoom)) + self.cursor:moveTo( + self.offset_x + t[l][w].x1 * self.globalzoom, + self.offset_y + self.cur_full_height - (t[l].y1 * self.globalzoom)) self.cursor:draw() end @@ -143,9 +154,8 @@ function DJVUReader:drawCursorBeforeWord(t, l, w) self.cursor:setHeight((t[l].y1 - t[l].y0) * self.globalzoom) self.cursor:moveTo( - t[l][w].x0*self.globalzoom - self.cursor.w, - self.offset_y + self.cur_full_height - - t[l].y1 * self.globalzoom) + self.offset_x + t[l][w].x0 * self.globalzoom - self.cursor.w, + self.offset_y + self.cur_full_height - t[l].y1 * self.globalzoom) self.cursor:draw() end @@ -154,7 +164,7 @@ function DJVUReader:startHighLightMode() local function _findFirstWordInView(t) for i=1, #t, 1 do - if self:_isWordInScreenRange(t[i][1]) then + if self:_isWordInScreenHeightRange(t[i][1]) then return i, 1 end end @@ -268,7 +278,7 @@ function DJVUReader:startHighLightMode() end if w.new ~= 0 and - not self:_isWordInScreenRange(t[l.new][w.new]) then + not self:_isWordInScreenHeightRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() self:goto(pageno) @@ -281,9 +291,13 @@ function DJVUReader:startHighLightMode() -- update cursor if w.cur == 0 then -- meet line left end, must be handled as special case - self:drawCursorBeforeWord(t, l.cur, 1) + if self:_isWordInScreenRange(t[l.cur][1]) then + self:drawCursorBeforeWord(t, l.cur, 1) + end else - self:drawCursorAfterWord(t, l.new, w.new) + if self:_isWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end end elseif ev.code == KEY_FW_RIGHT then local is_next_view = false @@ -301,7 +315,7 @@ function DJVUReader:startHighLightMode() end if w.new ~= 0 and - not self:_isWordInScreenRange(t[l.new][w.new]) then + not self:_isWordInScreenHeightRange(t[l.new][w.new]) then local pageno = self:nextView() self:goto(pageno) is_next_view = true @@ -311,9 +325,13 @@ function DJVUReader:startHighLightMode() if w.cur == 0 then -- meet line left end, must be handled as special case - self:drawCursorBeforeWord(t, l.new, 1) + if self:_isWordInScreenRange(t[l.new][1]) then + self:drawCursorBeforeWord(t, l.new, 1) + end else - self:drawCursorAfterWord(t, l.new, w.new) + if self:_isWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end end elseif ev.code == KEY_FW_UP then local is_next_view = false @@ -328,8 +346,8 @@ function DJVUReader:startHighLightMode() end if w.new ~= 0 and - not self:_isWordInScreenRange(t[l.new][w.new]) - or w.new == 0 and not self:_isWordInScreenRange(t[l.new][1]) then + not self:_isWordInScreenHeightRange(t[l.new][w.new]) + or w.new == 0 and not self:_isWordInScreenHeightRange(t[l.new][1]) then -- goto next view of current page local pageno = self:prevView() self:goto(pageno) @@ -339,9 +357,13 @@ function DJVUReader:startHighLightMode() end if w.new == 0 then - self:drawCursorBeforeWord(t, l.new, 1) + if self:_isWordInScreenRange(t[l.new][1]) then + self:drawCursorBeforeWord(t, l.new, 1) + end else - self:drawCursorAfterWord(t, l.new, w.new) + if self:_isWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end end elseif ev.code == KEY_FW_DOWN then local is_next_view = false @@ -354,8 +376,8 @@ function DJVUReader:startHighLightMode() end if w.cur ~= 0 and - not self:_isWordInScreenRange(t[l.new][w.new]) - or w.cur == 0 and not self:_isWordInScreenRange(t[l.new][1]) then + not self:_isWordInScreenHeightRange(t[l.new][w.new]) + or w.cur == 0 and not self:_isWordInScreenHeightRange(t[l.new][1]) then -- goto next view of current page local pageno = self:nextView() self:goto(pageno) @@ -365,9 +387,13 @@ function DJVUReader:startHighLightMode() end if w.cur == 0 then - self:drawCursorBeforeWord(t, l.new, 1) + if self:_isWordInScreenRange(t[l.new][1]) then + self:drawCursorBeforeWord(t, l.new, 1) + end else - self:drawCursorAfterWord(t, l.new, w.new) + if self:_isWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end end elseif ev.code == KEY_DEL then if self.highlight[self.pageno] then @@ -416,7 +442,7 @@ function DJVUReader:startHighLightMode() end if w.new ~= 0 and - not self:_isWordInScreenRange(t[l.new][w.new]) then + not self:_isWordInScreenHeightRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() self:goto(pageno) @@ -449,7 +475,7 @@ function DJVUReader:startHighLightMode() is_meet_end = true end - if not self:_isWordInScreenRange(t[l.new][w.new]) then + if not self:_isWordInScreenHeightRange(t[l.new][w.new]) then local pageno = self:nextView() self:goto(pageno) From e5acbeb363b990c8f844a49902e6c41d348b3c60 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 24 Mar 2012 18:28:52 +0800 Subject: [PATCH 021/183] mod: fix bug in cursor move only move cursor to word in current view --- djvureader.lua | 28 ++++++++++++++-------------- graphics.lua | 11 +++++++++-- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index b4039145d..c605ad70c 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -277,15 +277,14 @@ function DJVUReader:startHighLightMode() l.new, w.new = _prevWord(t, l.cur, w.cur) end + self.cursor:clear() if w.new ~= 0 and - not self:_isWordInScreenHeightRange(t[l.new][w.new]) then + not self:_isWordInScreenHeightRange(t[l.new][w.new]) + and self:_isWordInScreenWidthRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() self:goto(pageno) is_next_view = true - else - -- no need to goto next view, clear previous cursor manually - self.cursor:clear() end -- update cursor @@ -314,13 +313,13 @@ function DJVUReader:startHighLightMode() end end + self.cursor:clear() if w.new ~= 0 and - not self:_isWordInScreenHeightRange(t[l.new][w.new]) then + not self:_isWordInScreenHeightRange(t[l.new][w.new]) + and self:_isWordInScreenWidthRange(t[l.new][w.new]) then local pageno = self:nextView() self:goto(pageno) is_next_view = true - else - self.cursor:clear() end if w.cur == 0 then @@ -345,15 +344,15 @@ function DJVUReader:startHighLightMode() l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) end - if w.new ~= 0 and - not self:_isWordInScreenHeightRange(t[l.new][w.new]) + self.cursor:clear() + if w.new ~= 0 + and not self:_isWordInScreenHeightRange(t[l.new][w.new]) + and self:_isWordInScreenWidthRange(t[l.new][w.new]) or w.new == 0 and not self:_isWordInScreenHeightRange(t[l.new][1]) then -- goto next view of current page local pageno = self:prevView() self:goto(pageno) is_next_view = true - else - self.cursor:clear() end if w.new == 0 then @@ -375,15 +374,16 @@ function DJVUReader:startHighLightMode() l.new, w.new = _wordInNextLine(t, l.cur, w.cur) end + self.cursor:clear() if w.cur ~= 0 and not self:_isWordInScreenHeightRange(t[l.new][w.new]) - or w.cur == 0 and not self:_isWordInScreenHeightRange(t[l.new][1]) then + and self:_isWordInScreenWidthRange(t[l.new][w.new]) + or w.cur == 0 + and not self:_isWordInScreenHeightRange(t[l.new][1]) then -- goto next view of current page local pageno = self:nextView() self:goto(pageno) is_next_view = true - else - self.cursor:clear() end if w.cur == 0 then diff --git a/graphics.lua b/graphics.lua index 2e357b1ae..267cb9469 100644 --- a/graphics.lua +++ b/graphics.lua @@ -42,6 +42,7 @@ Cursor = { h = 10, w = nil, line_w = nil, + is_cleared = true, } function Cursor:new(o) @@ -76,11 +77,17 @@ function Cursor:_draw(x, y) end function Cursor:draw() - self:_draw(self.x_pos, self.y_pos) + if self.is_cleared then + self.is_cleared = false + self:_draw(self.x_pos, self.y_pos) + end end function Cursor:clear() - self:_draw(self.x_pos, self.y_pos) + 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) From e15fc5e21e123f3a5af656978e8fcbd294a26e5d Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 24 Mar 2012 18:39:33 +0800 Subject: [PATCH 022/183] mod: delete useless variable is_next_view --- djvureader.lua | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index c605ad70c..17031b859 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -263,7 +263,6 @@ function DJVUReader:startHighLightMode() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then if ev.code == KEY_FW_LEFT then - local is_next_view = false if w.cur == 1 then w.cur = 0 w.new = 0 @@ -278,13 +277,12 @@ function DJVUReader:startHighLightMode() end self.cursor:clear() - if w.new ~= 0 and - not self:_isWordInScreenHeightRange(t[l.new][w.new]) + if w.new ~= 0 + and not self:_isWordInScreenHeightRange(t[l.new][w.new]) and self:_isWordInScreenWidthRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() self:goto(pageno) - is_next_view = true end -- update cursor @@ -299,7 +297,6 @@ function DJVUReader:startHighLightMode() end end elseif ev.code == KEY_FW_RIGHT then - local is_next_view = false if w.cur == 0 then w.cur = 1 w.new = 1 @@ -319,7 +316,6 @@ function DJVUReader:startHighLightMode() and self:_isWordInScreenWidthRange(t[l.new][w.new]) then local pageno = self:nextView() self:goto(pageno) - is_next_view = true end if w.cur == 0 then @@ -333,7 +329,6 @@ function DJVUReader:startHighLightMode() end end elseif ev.code == KEY_FW_UP then - local is_next_view = false if w.cur == 0 then -- goto left end of last line l.new = math.max(l.cur - 1, 1) @@ -348,11 +343,11 @@ function DJVUReader:startHighLightMode() if w.new ~= 0 and not self:_isWordInScreenHeightRange(t[l.new][w.new]) and self:_isWordInScreenWidthRange(t[l.new][w.new]) - or w.new == 0 and not self:_isWordInScreenHeightRange(t[l.new][1]) then + or w.new == 0 + and not self:_isWordInScreenHeightRange(t[l.new][1]) then -- goto next view of current page local pageno = self:prevView() self:goto(pageno) - is_next_view = true end if w.new == 0 then @@ -365,7 +360,6 @@ function DJVUReader:startHighLightMode() end end elseif ev.code == KEY_FW_DOWN then - local is_next_view = false if w.cur == 0 then -- on the left end of current line, -- goto left end of next line @@ -383,7 +377,6 @@ function DJVUReader:startHighLightMode() -- goto next view of current page local pageno = self:nextView() self:goto(pageno) - is_next_view = true end if w.cur == 0 then From 5d087d0a84341a0756f73f7738288bbbe647f787 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 24 Mar 2012 20:12:00 +0800 Subject: [PATCH 023/183] fix: handle out or view range word in highlight mode --- djvureader.lua | 58 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 17031b859..882ddf38d 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -436,9 +436,13 @@ function DJVUReader:startHighLightMode() if w.new ~= 0 and not self:_isWordInScreenHeightRange(t[l.new][w.new]) then - -- word is in previous view - local pageno = self:prevView() - self:goto(pageno) + -- word out of left and right sides of current view should + -- not trigger pan by page + if self:_isWordInScreenWidthRange(t[l.new][w.new]) then + -- word is in previous view + local pageno = self:prevView() + self:goto(pageno) + end local l0 = l.start local w0 = w.start @@ -469,8 +473,10 @@ function DJVUReader:startHighLightMode() end if not self:_isWordInScreenHeightRange(t[l.new][w.new]) then - local pageno = self:nextView() - self:goto(pageno) + if self:_isWordInScreenWidthRange(t[l.new][w.new]) then + local pageno = self:nextView() + self:goto(pageno) + end local tmp_l = l.start local tmp_w = w.start @@ -505,26 +511,32 @@ function DJVUReader:startHighLightMode() l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) end -- EOF if is not is_meet_end elseif ev.code == KEY_FW_UP then - if l.cur == 1 then - -- handle left end of first line as special case - tmp_l = 1 - tmp_w = 0 - else - tmp_l, tmp_w = _wordInPrevLine(t, l.cur, w.cur) - end - while not (tmp_l == l.cur and tmp_w == w.cur) do - l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) + is_meet_end = false + if not is_meet_start then + if l.cur == 1 then + -- handle left end of first line as special case + tmp_l = 1 + tmp_w = 0 + else + tmp_l, tmp_w = _wordInPrevLine(t, l.cur, w.cur) + end + while not (tmp_l == l.cur and tmp_w == w.cur) do + l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) + end end elseif ev.code == KEY_FW_DOWN then - if w.cur == 0 then - -- handle left end of first line as special case - tmp_l = math.min(tmp_l + 1, #t) - tmp_w = 1 - else - tmp_l, tmp_w = _wordInNextLine(t, l.new, w.new) - end - while not (tmp_l == l.cur and tmp_w == w.cur) do - l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) + is_meet_start = false + if not is_meet_end then + if w.cur == 0 then + -- handle left end of first line as special case + tmp_l = math.min(tmp_l + 1, #t) + tmp_w = 1 + else + tmp_l, tmp_w = _wordInNextLine(t, l.new, w.new) + end + while not (tmp_l == l.cur and tmp_w == w.cur) do + l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) + end end elseif ev.code == KEY_FW_PRESS then local l0, w0, l1, w1 From 74d17602600f7a83966c47977c79e966bc94d28c Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 24 Mar 2012 21:27:35 +0800 Subject: [PATCH 024/183] fix: start position of cursor --- djvureader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djvureader.lua b/djvureader.lua index 882ddf38d..016661d2e 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -164,7 +164,7 @@ function DJVUReader:startHighLightMode() local function _findFirstWordInView(t) for i=1, #t, 1 do - if self:_isWordInScreenHeightRange(t[i][1]) then + if self:_isWordInScreenRange(t[i][1]) then return i, 1 end end From 0c2afd805ef8a18e8d701c95d754536a51b748b1 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 24 Mar 2012 22:06:29 +0800 Subject: [PATCH 025/183] fix: enable pan by page from right end to next line left end --- djvureader.lua | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 016661d2e..d8dafa465 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -303,17 +303,21 @@ function DJVUReader:startHighLightMode() else l.new, w.new = _nextWord(t, l.cur, w.cur) if w.new == 1 then - -- Must be come from the right end of previous line, so - -- goto the left end of current line. + -- Must be come from the right end of previous line, + -- so goto the left end of current line. w.cur = 0 w.new = 0 end end self.cursor:clear() - if w.new ~= 0 and - not self:_isWordInScreenHeightRange(t[l.new][w.new]) - and self:_isWordInScreenWidthRange(t[l.new][w.new]) then + + local tmp_w = w.new + if w.cur == 0 then + tmp_w = 1 + end + if not self:_isWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isWordInScreenWidthRange(t[l.new][tmp_w]) then local pageno = self:nextView() self:goto(pageno) end @@ -340,11 +344,13 @@ function DJVUReader:startHighLightMode() end self.cursor:clear() - if w.new ~= 0 - and not self:_isWordInScreenHeightRange(t[l.new][w.new]) - and self:_isWordInScreenWidthRange(t[l.new][w.new]) - or w.new == 0 - and not self:_isWordInScreenHeightRange(t[l.new][1]) then + + local tmp_w = w.new + if w.cur == 0 then + tmp_w = 1 + end + if not self:_isWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isWordInScreenWidthRange(t[l.new][tmp_w]) then -- goto next view of current page local pageno = self:prevView() self:goto(pageno) @@ -369,11 +375,13 @@ function DJVUReader:startHighLightMode() end self.cursor:clear() - if w.cur ~= 0 and - not self:_isWordInScreenHeightRange(t[l.new][w.new]) - and self:_isWordInScreenWidthRange(t[l.new][w.new]) - or w.cur == 0 - and not self:_isWordInScreenHeightRange(t[l.new][1]) then + + local tmp_w = w.new + if w.cur == 0 then + tmp_w = 1 + end + if not self:_isWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isWordInScreenWidthRange(t[l.new][tmp_w]) then -- goto next view of current page local pageno = self:nextView() self:goto(pageno) From eac989395888822781ae6c2fedc923c0350914fa Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 26 Mar 2012 20:19:48 +0800 Subject: [PATCH 026/183] fix: add KEY_LPGFWD and KEY_LPGBCK to filechooser --- filechooser.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/filechooser.lua b/filechooser.lua index c4e2f1378..b8ea6d3b9 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -197,7 +197,7 @@ function FileChooser:choose(ypos, height) 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 +208,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 From 11137599720ebe845998cd7c85e239919cb6db97 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 26 Mar 2012 20:25:44 +0800 Subject: [PATCH 027/183] fix: add virtual startHighLightMode method for all readers. --- unireader.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/unireader.lua b/unireader.lua index f607e99ef..58a1f2843 100644 --- a/unireader.lua +++ b/unireader.lua @@ -113,6 +113,11 @@ end -- You need to overwrite following two methods if your -- reader supports highlight feature. ---------------------------------------------------- + +function UniReader:startHighLightMode() + return +end + function UniReader:highLightText() return end From a8c40cd5b64b78c6534da3ebd2ecb5b76b8d8d45 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 27 Mar 2012 01:15:58 +0800 Subject: [PATCH 028/183] fix: highlight words that partially fit into screen --- djvureader.lua | 72 +++++++++++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index d8dafa465..cd0ad9885 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -33,14 +33,14 @@ function DJVUReader:_rectCoordTransform(x0, y0, x1, y1) (y1 - y0) * self.globalzoom end - -function DJVUReader:_isWordInScreenRange(w) - return self:_isWordInScreenHeightRange(w) and - self:_isWordInScreenWidthRange(w) +-- make sure the whole word can be seen in screen +function DJVUReader:_isEntireWordInScreenRange(w) + return self:_isEntireWordInScreenHeightRange(w) and + self:_isEntireWordInScreenWidthRange(w) end -- y axel in djvulibre starts from bottom -function DJVUReader:_isWordInScreenHeightRange(w) +function DJVUReader:_isEntireWordInScreenHeightRange(w) return (w ~= nil) and (self.cur_full_height - (w.y1 * self.globalzoom) >= -self.offset_y) and @@ -48,17 +48,28 @@ function DJVUReader:_isWordInScreenHeightRange(w) -self.offset_y + height) end -function DJVUReader:_isWordInScreenWidthRange(w) +function DJVUReader:_isEntireWordInScreenWidthRange(w) return (w ~= nil) and (w.x0 * self.globalzoom >= -self.offset_x) and (w.x1 * self.globalzoom <= -self.offset_x + width) end +-- make sure at least part of the word can be seen in screen +function DJVUReader:_isWordInScreenRange(w) + return (w ~= nil) and + (self.cur_full_height - (w.y0 * self.globalzoom) >= + -self.offset_y) and + (self.cur_full_height - (w.y1 * self.globalzoom) <= + -self.offset_y + height) and + (w.x1 * self.globalzoom >= -self.offset_x) and + (w.x0 * self.globalzoom <= -self.offset_x + width) +end + function DJVUReader:toggleTextHighLight(word_list) for _,text_item in ipairs(word_list) do for _,line_item in ipairs(text_item) do -- make sure that line is in screen range - if self:_isWordInScreenHeightRange(line_item) then + if self:_isEntireWordInScreenHeightRange(line_item) then local x, y, w, h = self:_rectCoordTransform( line_item.x0, line_item.y0, line_item.x1, line_item.y1) @@ -79,7 +90,7 @@ function DJVUReader:toggleTextHighLight(word_list) elseif self.highlight.drawer == "marker" then fb.bb:invertRect(x, y, w, h) end - end -- EOF if isWordInScreenHeightRange + end -- EOF if isEntireWordInScreenHeightRange end -- EOF for line_item end -- EOF for text_item end @@ -135,7 +146,8 @@ function DJVUReader:_toggleTextHighLight(t, l0, w0, l1, w1) end for _l, _w in self:_wordIterFromRange(t, l0, w0, l1, w1) do - if self:_isWordInScreenHeightRange(t[_l][_w]) then + if self:_isWordInScreenRange(t[_l][_w]) then + -- blitbuffer module will take care of the out of screen range part. self:_toggleWordHighLight(t, _l, _w) end end @@ -164,7 +176,7 @@ function DJVUReader:startHighLightMode() local function _findFirstWordInView(t) for i=1, #t, 1 do - if self:_isWordInScreenRange(t[i][1]) then + if self:_isEntireWordInScreenRange(t[i][1]) then return i, 1 end end @@ -278,8 +290,8 @@ function DJVUReader:startHighLightMode() self.cursor:clear() if w.new ~= 0 - and not self:_isWordInScreenHeightRange(t[l.new][w.new]) - and self:_isWordInScreenWidthRange(t[l.new][w.new]) then + and not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) + and self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() self:goto(pageno) @@ -288,11 +300,11 @@ function DJVUReader:startHighLightMode() -- update cursor if w.cur == 0 then -- meet line left end, must be handled as special case - if self:_isWordInScreenRange(t[l.cur][1]) then + if self:_isEntireWordInScreenRange(t[l.cur][1]) then self:drawCursorBeforeWord(t, l.cur, 1) end else - if self:_isWordInScreenRange(t[l.new][w.new]) then + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then self:drawCursorAfterWord(t, l.new, w.new) end end @@ -316,19 +328,19 @@ function DJVUReader:startHighLightMode() if w.cur == 0 then tmp_w = 1 end - if not self:_isWordInScreenHeightRange(t[l.new][tmp_w]) - and self:_isWordInScreenWidthRange(t[l.new][tmp_w]) then + if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then local pageno = self:nextView() self:goto(pageno) end if w.cur == 0 then -- meet line left end, must be handled as special case - if self:_isWordInScreenRange(t[l.new][1]) then + if self:_isEntireWordInScreenRange(t[l.new][1]) then self:drawCursorBeforeWord(t, l.new, 1) end else - if self:_isWordInScreenRange(t[l.new][w.new]) then + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then self:drawCursorAfterWord(t, l.new, w.new) end end @@ -349,19 +361,19 @@ function DJVUReader:startHighLightMode() if w.cur == 0 then tmp_w = 1 end - if not self:_isWordInScreenHeightRange(t[l.new][tmp_w]) - and self:_isWordInScreenWidthRange(t[l.new][tmp_w]) then + if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then -- goto next view of current page local pageno = self:prevView() self:goto(pageno) end if w.new == 0 then - if self:_isWordInScreenRange(t[l.new][1]) then + if self:_isEntireWordInScreenRange(t[l.new][1]) then self:drawCursorBeforeWord(t, l.new, 1) end else - if self:_isWordInScreenRange(t[l.new][w.new]) then + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then self:drawCursorAfterWord(t, l.new, w.new) end end @@ -380,19 +392,19 @@ function DJVUReader:startHighLightMode() if w.cur == 0 then tmp_w = 1 end - if not self:_isWordInScreenHeightRange(t[l.new][tmp_w]) - and self:_isWordInScreenWidthRange(t[l.new][tmp_w]) then + if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then -- goto next view of current page local pageno = self:nextView() self:goto(pageno) end if w.cur == 0 then - if self:_isWordInScreenRange(t[l.new][1]) then + if self:_isEntireWordInScreenRange(t[l.new][1]) then self:drawCursorBeforeWord(t, l.new, 1) end else - if self:_isWordInScreenRange(t[l.new][w.new]) then + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then self:drawCursorAfterWord(t, l.new, w.new) end end @@ -443,10 +455,10 @@ function DJVUReader:startHighLightMode() end if w.new ~= 0 and - not self:_isWordInScreenHeightRange(t[l.new][w.new]) then + not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then -- word out of left and right sides of current view should -- not trigger pan by page - if self:_isWordInScreenWidthRange(t[l.new][w.new]) then + if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() self:goto(pageno) @@ -480,8 +492,8 @@ function DJVUReader:startHighLightMode() is_meet_end = true end - if not self:_isWordInScreenHeightRange(t[l.new][w.new]) then - if self:_isWordInScreenWidthRange(t[l.new][w.new]) then + if not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then + if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then local pageno = self:nextView() self:goto(pageno) end From 6886ae983040d54ed06b814a01a872cdc8212d5d Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 26 Mar 2012 20:16:36 +0200 Subject: [PATCH 029/183] exit reader with just Home as before #55 --- unireader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unireader.lua b/unireader.lua index bf956be15..6575d7cf1 100644 --- a/unireader.lua +++ b/unireader.lua @@ -972,7 +972,7 @@ function UniReader:addAllCommands() function(unireader) unireader:screenRotate("anticlockwise") end) - self.commands:add(KEY_HOME,MOD_SHIFT_OR_ALT,"Home", + self.commands:add(KEY_HOME,nil,"Home", "exit application", function(unireader) keep_running = false From b496c2081f26a026e83cb26c473c8f57943111df Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 26 Mar 2012 20:35:51 +0200 Subject: [PATCH 030/183] ignore djvulibre and generated zips --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8a050081b..21f649e41 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,13 @@ lua lua-* .reader.kpdfview.lua mupdf-thirdparty.zip +djvulibre* kpdfview *.o +kindlepdfviewer-*.zip /.cproject /.project /.reader.kpdfview + From 8159b24a4676b7f58f928104bdfcd28932a673e5 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 27 Mar 2012 02:45:44 +0800 Subject: [PATCH 031/183] add: simple highlight lists menu --- unireader.lua | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/unireader.lua b/unireader.lua index 58a1f2843..740147f4d 100644 --- a/unireader.lua +++ b/unireader.lua @@ -724,10 +724,10 @@ function UniReader:showTOC() local menu_items = {} local filtered_toc = {} -- build menu items - for _k,_v in ipairs(self.toc) do + for k,v in ipairs(self.toc) do table.insert(menu_items, - (" "):rep(_v.depth-1)..self:cleanUpTOCTitle(_v.title)) - table.insert(filtered_toc,_v.page) + (" "):rep(v.depth-1)..self:cleanUpTOCTitle(v.title)) + table.insert(filtered_toc,v.page) end toc_menu = SelectMenu:new{ menu_title = "Table of Contents", @@ -744,9 +744,9 @@ end function UniReader:showJumpStack() local menu_items = {} - for _k,_v in ipairs(self.jump_stack) do + for k,v in ipairs(self.jump_stack) do table.insert(menu_items, - _v.datetime.." -> Page ".._v.page.." ".._v.notes) + v.datetime.." -> Page "..v.page.." "..v.notes) end jump_menu = SelectMenu:new{ menu_title = "Jump Keeper (current page: "..self.pageno..")", @@ -762,6 +762,29 @@ function UniReader:showJumpStack() end end +function UniReader:showHighLight() + local menu_items = {} + local highlight_dict = {} + -- build menu items + for k,v in pairs(self.highlight) do + if type(k) == "number" then + for k1,v1 in ipairs(v) do + table.insert(menu_items, v1.text) + table.insert(highlight_dict, {page=k, start=v1[1]}) + end + end + end + toc_menu = SelectMenu:new{ + menu_title = "HighLights", + item_array = menu_items, + no_item_msg = "No HighLight found.", + } + item_no = toc_menu:choose(0, fb.bb:getHeight()) + if item_no then + self:goto(highlight_dict[item_no].page) + end +end + function UniReader:showMenu() local ypos = height - 50 local load_percent = (self.pageno / self.doc:getPages()) @@ -1015,6 +1038,12 @@ function UniReader:addAllCommands() unireader:startHighLightMode() unireader:goto(unireader.pageno) end) + self.commands:add(KEY_N, MOD_SHIFT, "N", + "display all highlights", + function(unireader) + unireader:showHighLight() + unireader:goto(unireader.pageno) + end) self.commands:add(KEY_HOME,MOD_SHIFT_OR_ALT,"Home", "exit application", function(unireader) From 461c00eb9806ee3694baa37c826c3eb69352885e Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 26 Mar 2012 21:28:14 +0200 Subject: [PATCH 032/183] test pdf file for two-column zoom #69 It also demonstrate nicely need to manually re-invoke two-column zoom which was first reported by vmonkey at http://www.mobileread.com/forums/showpost.php?p=1992328&postcount=119 I'm not quite sure that I prefer manual interaction any more :-) This file is created using Inkscape and pdftk, so it's free for re-distribution as a test suite, possibly using something like Perceptual Image Diff http://pdiff.sourceforge.net/ to automate it. --- test/2col.pdf | Bin 0 -> 10410 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/2col.pdf diff --git a/test/2col.pdf b/test/2col.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6b91d29ed6a99d5b935be977800f9887c75e89ea GIT binary patch literal 10410 zcmc(lRa_iP_pWgn+#LpY8Qk6732rmEJ0TF<-8BIM1cC(%?(RW?I|L8zmP7Xb%l^)H zFJs7RqI{V-Bo?@yuYGVmzHG(v2g=vx3(u|0Ng+hptFS?5FjiJV3+ankkj%o z_kjF;YR>WZzoj`41Ynnvc>NU>{f{ri3H+Cg=bw66TL%w_8<1Vr;kAY|#L^iI0kA7W zoUA=;fIK|hJb$$A9&QkGM}SYRV~#>RsZhGi-Z#LyxIVGpHm4ghyd0xDCaARH8P;&IO=~d$Ojc$y|lep_3#i7mMRKuit_!$YppwwIk%lwBG~2e zEg8x8Wg^9o>qRW2RUYBGfvGMg&(4W^CDbeP7~jL|A6dHP)I#|-*5#BpSs348V)+Ty z*}O+zN_;j(&EU8fK3K@#t5ctx7=Bi+X}745r0-m!T^qQIF3VF{Kb*{3dVYI^S|ik5 z?U6@ouT*AT`4C!(EyeO&QQTqB(N|xpX2JS-he=+D-QTruX>wt?JdMHQfthzILx0{^ z$>E@_`oc~pV@2lZ-HnHR-Xy+&1?)*()bVXyaT=HDJM4bU%6@a(@UJ%3LG?mvZJ~$` z-`n|!J#tc0L*HV2X2`Hw-90ckN+zAGdL-Bhl+csqsp5hsELkq1`Zf1Cs?Xw~~V%R4PAKT~pr*sY&#?AFXtB+?ckn|#x_2Mj;ROb}CN zvw8CsR=j!Gd8OsM&1z~Wy&W;t3RRO2>NOn}5WVHgsDwO<#-ukcnFN7#GWG}GuDLwB zE8(eHSLOzCv7@P+T??vO3}$7j=E!Pt^(x;{7OWd5elMDzCL!~;|E)8rS)4O-6A$4! z?Ihl*rVjK}X74VJKvvUANn)OkV!)MbeJ7Qa^rPI~n8x*47-4vkDv>AG7%?5lb~#Ko z=@F)vB;sW|RoB6CcN;GMV=|jdZYbe^LSHT{Gb~IrjlVd~x}{resI*GkMBht4OfAv4 zWI)g$QBFSNc@<_Kr-pg{E2YGXxZCXmdd19QF^fLUdRY!`0Sg}^W{bhkdGK)%tIo}$ z>GY?GpxTt^=ZOZm6%VWc^>mu21QtMbk!Kn?!VE+=)CSpV#ZZdUvxT2sybclnd>T3F zhsUN!>4;V6{L)9-{&WJM{NO+U8B!1SKI>`U^EBT~XjouKZ1X5XuuXZg*$JWE@)5?l z!;pV8g8zDm3nTQ3M_8(IN7+J0`^S7r#xw@o;p)(ejiSz&$l)#WAjG10_&|qE<~ZZT zYQ*H7{^dy5dwOZa&oLo%3`5o4S=vdA1)T**m-#sjpzL?|R01OAIboi|B_;CC7|)Ya zme7hx<*Xr3$Vnzwhd6y$tj;9D-ZYDA8cTEB1seTJRNFQdIzzpXR9@7#AhXITmBkK^ zvjxM#W7TMNV$DOV2v#k*oAW3SIczp`BB=&xUiwJJ9uz*qdvTKvi%b+?bI5LnSa&6E z)~Zgc+Ofov!A>udn9*CyG63pmI1(I{i7Ib%?n+0N>neYiXe9ma#jwFqsfWHS3ASS*R++iAGOSAdK2a1S{$rMBWHT!p4kN}z#DmGNmGO6j3}+Qu?oh` zkd_RtJRTt@)3GpqL=x2)gGp9~e^{h3ngf1yTWbgqa=Ch`={;oB9i?(V)y1j3ErRlH zEU?#cRe3Y?TLywv)XAS$)Ye}jI4_sFcvUXcg@&0Va=St8fqbx{-fku2zC?6-P*N4c zCC@Iz2JTx{3k63l*5ZK1e&2Qq_WO`v@UYz@`1YwC)o=9(>36-e#>uu(d+I zQ6x>Un=hNfh#jNU8VOoiY{x#yIBpBqhQgo$`gv-?$HCEow7vwzmO}R-xa8P;gw`d@ zTw7Pz#Ge}}(_gxeZM}UQ={q?mHGfew6%!EX4_vNcYT9rTo+z{=;0k59DiYG+4s+@( z7AY;2Ne8(BQ)HqAj6aGV^>?5(hiELysZXxZ9vd=b%b%^0U}#hCwVl(v@8pshrh!1O ztz{c5VA80fwI_(LJ+-rIY^_9iu?ov`Z?|Ku9z~?05%rx=^YZPoV)WvZ=M>{i_Fsq< zStwwkg-O^=O`JS7P!{X&br|uxgppPOU46VD#ARD5;wn{T8#)SXmn;I6IMaLO7y%K@ z12qen&obHZ1L)z&?#>!tLY0bhvmVSL46X!_{XC$)unq$GPuJmYm;42D?Q`rFJfZkP ze7)iwmf=~2-A`_C?+i7OB|eZr1mA&-oIK!*9%y5zNQL5Q(!aI|iVCKe)i1{wA~ZF( z%V5)C?T!U64Dq@wxovAryIJ>^Tt=$Q?=zF$Y~PxQwbW3a9w?UX2P9gTw4xN|dgZ_cM-5zMZS~$Vdk)x^hA8G_9sf**mpV zJH~|qZ!qV)vmAVG_nHI%zs>xLw*)gPECjP6SzUQ4wo^Bkc4^dSmEz8v^F7}u(@@3{ z{&@k#cWDXxMsNB4yl0BvZpp{L+8tZnP$wA4ZkS?Pa5B;m!S*lB+ppF``zc}!{&Cgwwjk`I7n_4LCH zWdNP{{pqQnMP2f?{gWSB@wgWd5rqN(r_6%Es7v@8jvsOI1ksV^w41AGGB0Wrq@1yWbW@^{8 za_Pg{=dNZtRcvtv&dBC$5g?wzStwGQ`O?sVJ;n2tK7-FODm#-$H%HzpyB9u*odNW! z_rMQ%JW|4Y^Z9*<+e2VoxkFB5uQ48;5di}J<-vN$++Vx8!{TlgZ1flqO zNh?e#Ue}xk2iWEZ)W(kcRV%(b*`lc6vJ<0DxtH)SszX6+Gjl6)`SibDn!>)P8_g&S z+c+z@p-fuBBG8^%M{uxJiqPjDUp38>ahpdJu(RwOmBPEv-nH8lr}b{ zp4T3Y$lTj$rbrj}&Mz*}=v+AQ*eNL?kZ_l69K>(9ZeOiRb*0dn*E*|OL*Lqx(yLMo zIdO;^m_`);XjWqj%L)Gm3`^!*DRo8`H^Ahv!{PUHUY?Fizzed#CHT#6yuSPABP>w` zm7VqZM+wQ^RGH^ndkLnj;G2r-mp7xui6#C)iVfSNK^R38VKoKXd* z4s{Y}n}Vn_?xioWEnVCPLc(_jop}UPz(t`UpGEU~KuF%FmWEL>MZvtf?>hF9u_Y`FDu-E1jsZ1&bC(wQJO<1BWld-{L)KL{~2~qmVqpIx! zm;Cq}A?G+3*U-olm|~QTIYE(n3VT!0c{U|`L|IiU?wM&i>Ar!Vz)+~>Ov2`Z)GBJy zn8y|@7?f;lMdUXKf?O5JDVmKHOTujEB`MI>YUf6oC4zM18{)rL7&#>(x^w8INv2&8 zJud7#YnFFBh|ft+e0%6pT;tT(#^kFiCGekU2%H@6GW%HAkkAYY)2NHsc{h*=EA|Eb zB{(Ho+@x#Yg1txcL+2XfB093<&AfE_`Wz8<1AL#kQC2i0-tvLI>GO1$k`%hg>&1sN zp_!@{I4ek9D{`<*O8;i?ILPeO9FBfgPAI*_RJ_`cY-9O#Z789Y8%>hv=bqt2)^!#< zeb-FQ1?YSjpc>5`A}2_<_h*q@o5UAnd+?)hdg|gF23{!Xq-;hyXXdc(_P(o<2FcQ@d1as>JRH-C@FzvpQJUIgCA|sC2o2!@eJHC0YNa*I z)d`|YxE%@Nwkm#(-K6(yEEghlmN>)0t%5PJ4FS%Q3GEQKV4k$;Ej-KB<>5OEdT;Rq zi$=t5o7rTf3rn~A0AP{MiNYOq!dJZXG_nVE_^#VB#4aZtswY!OARlH z)4|{K^QA3pV#zoXEX@X4>vc5J&f^0;rrmX0kXK!E_{VVZ%OFeZAD&ga5^)5E)~Kw& z8k#;-mR>(VUp`^%xYCjKHy$RAT1pJcL%w`qu^1Fn9>(H`H;4Wt)1d)pl(@Egl?}}; zwJAn9g#Im{{YC=AMrvrEs<|wgs}ndh=C5@12|yT;o_I#_VPaVOnC+N?)(zwFOHNr6 zll%&?_#o=9b%0C$H#CU8;nGh;ZBkp>!YS+sgzuSH>myB47mcWH$TtZL;<^*9a|tjG zNMwYBLaJYXZm-Xi@n;ka;~@8~yS-_3{OSnsrIfPMB1v(v`_cMY?La2MeRuUwz z)K9~X14GoBPi%G(x(LB=*$T_I_EI|n%7!EmGq1)XL$Bu8cNSi07yiC(;rXx~+F~tF zv7dbBSUfXXoZWAf?I>kg1Ak6@4akFH^^T^ zCM$n&rC6JTDX26qva&NZH#j0dF>Yh<0r6xCR@nmjym*pUt-j~#bB5pZxF=`S199-7 z38i0L)d9>#Lw`^fQ|j0Jbw`Bt$xjEUy5h$bGyFh%|OL~=mo z7RwaB=*(ako|&C&rI08jM0qf>!#K+!zWbR~wyRr*0{ZiVKe&rNCe=x!#!GX&S3}m# z9gS~id8sZ0CgzEE2yD4Ol~zA60Man>quLm;?-AV zpP6odWygKe%vuqTosCkiX(=2FTCk}ujzw|L-w|0fl6)Yo9RE0@(7Y&WVz-Ms(A!<) z;jgHJO0UcrEGemUOBj1l__4(%FV1npcXi^rlND#OX5Zat+k{*Kq--gw(2bgXezOeg zEnSP2tnkJgB@CNlseG`A8UD>tBMxvr6NkzK+&QSk*orT&8ZyWc*H{(ek0c6WD6Esh zOB%Ra`heTY_~FYcem1N%AqH$-V6$Tb<*y64U!y0zmxpNG<@2e$I~!rqw8hMkWT_A< zFj)e#{D9oC%i)&6sn-YT5NY19(X2qt5(!77duNvovL6>81G&+kANZ|8@ZnN46c9tR zDVCpMFMJcHV=_f2*AhLSUk)(>(VbiguVALCq8ddQb*F-l_=F4ye;k&y@JkgM4>=;% z6ppDHdwv#PR-S)2wJ6$=cM>_D3c$ zaOvTSNq@lJ=iLQT9uBVWL z!2ZfN%oPR*7?%E;H^?mI;BpMvPExFG{FzLN6WmUcX?Q&~MM0M1eCZ)B;9eQ;& zHd9QRRo-KD!v!T%4zr)@^QM520rM^f)|nD=fM+ibT$`si{KMK&W*))Wi=zWQTk~eF zk#^YmLP|MO|K`nO`;?SrKyM0hTh<$?y;G#|1i(I8OMzIrvtEGk!bd0@r^Ug`JC;e% zhpz{pBG5yqd`wk2(&46mF+LjX`T@Yu`#4t1K5a(Tq{^Yw9DomXR@v8ch}N4S$Wo?f zJ#Cm~PE}{toeN~q1q0BdqXn@yoBpU!Y-`x}Meo_Ebc6HyxItX| zW>0(QC-7Lc%t(Nw%8K9|8sT1YE6A*As4g*#97A?h?8AO_Lk?m{7repMC3+MnYDf>KI1t9>3DlX~xndR}qU@(|P^YWekqdVDM zTolKH?&T2LV#HmTtGo8`Fgb#T2I`DKC9OgFS+9-~#ms?&AO90ax~&L6qFt_Jor0`z zafIDKSWqX^lp{X})4MLK1ulRCMI@WDZLXZPX_as=F<~+CF-7X)jM++z^tTG%+Iy`G z(?PL>--zTkJ~x0@r}uBq7Wn7zFVmOnpQf*zo3p3O-$t>N7LZ*U;$>?I(Ug+}uxtCe zK!EIjwFB5+o3!1`o!ni_-5^euzJIJ>cJ)8U9zf7*IZXh&3IuFxF6rz8G~{><@Cfj* zaq{u-@NonAxj}6F`~rM@AY%Z#CdA#@)6Ej%4*X+XzsmmZMcv%`)oYe=cJg?w=nnj2 zOKbk=<3BH%|26Rc;&Ti9Q~!VRxw*Lcxc|lH4%63<|Jj1}e9P1p#GlMy_eM4yld$Yo z^o!UY zO`3NvovD8)J>JiEP9*dO%&_}i-HIISh#46j{qW3f&tO&%d7sB>tabC?UTH{?u^V+Q zc!v2RjBi`ys>6Zf-6wdmZ)xOywHF_8wg-~1``IryA*^kh3s-#Y6}PmWs#~pp!q zM+gIfn9oL?FD{xP3mS{5C-|uG(LZB{+DMbqC*_+l=iFv8Ui^%^ztIKd#@dHp zo|Okoc^NxZH^I6{3wv3J?+-rP)sXpi9ScKWGCQ?W;wo3cvoo(qUyZql%YM?VhxY%X zZ^i1oUu?`6_w1KsFR2pI6QXHP-+c_$ z@n=%~24CJfvH>Pq_Ijuu1g5s)&Xfs8(So@|UK@c+c( zmMV)~-4;5)dMOZph>r+l9Z;+z9RDgZ>zjGZALtF$l>G%|eGDQ)F_j-6fhtyIxAQfh+Cps z*GJ)~_LL$bYHtpy*kcfP*}Ar^+^Q)|4O20>oseYua1yG5qrO{lG{b_F>#461ar{Y9 z*r2uq4*9AmcMo)M$=$IIWa(KU;qWc!5BD1Qo^u4LOxdcZ)&lF zVGqZncNt?4`^3qjynra&j*w>?5#4x^-r5{2H~eyFj5T@>E#OEfJ9t*3f>M}uZnt@* zo-W?H6uIqFbzRbK$X*~mlVG0T;;4|BL|>D1>TsAhj5aJFxF zv+jG|mB*b-5e5@kd1SKCd-UU)4V!K>y&soJ^x<^t{>`Wm*x(4$H_w=OkuPFkuGg?P zrkowXN5bu_PeH;WNqSJud_$6@dXZaAkfe%@qviq!ji63>2mjc2&8li1r1h-{j`!`n zp#Sj+I#wMip-spFbOb3bxVP3%rYZ=mb#H)mmTbj-G}_oUs=+Y*!}>XJ`QR9AKD z{E|V-Q5gZfgYW0XRIoK2+L(QmHRLPA3rS;J@F1zdQLg>@#oc`_H<<8zP}5|c4pj>x zzs{`I?HZDP$2xGF?e`!wMKnG0`s~};6Es!iVt%hnH_Qf-LYo}j4Se(bjq;3-NJ5d< z3I9>4 z_ZNzhl{z@b=ZVF32dNeQ*I={ok_= zDs!yNZNRqgrFFX#-s0U0I0FL3h=55L9vOSq{2G??wsE z-lc+9y5dUyz{)6nO&lpl!^`UpGGPxwiI{z)p*bM6bTVDKxMeV(Rf7F^`Wtu^tLWzn z066biF<_5zA{j0pNyQR^0LC# zAkb5_!X@88ED$9>$X47%TbhuIv7+_q^F_5}Ba60SQER>~&>-;8m>RXy~++yric zb6XbU$Z6}z+}PN5>6t;@dvricW%8O8(cpWJ5l!GTfwr*UVUCmF z4OOE=T@o}XD?GbCc`nf8K5y@n>ut4@SBO8J>D$h=qG(YRl)Y8LKMLVL9=~7fQ4i(W z5Zavd~{}rx{?j>I})b}eaBK3n)y*Kb(iF)!HsU^*s zI_ud)4T zMIU9l*Fk2hdWOOExFC|?TB0W0u+*CluPL*OnS{QD)XvbEwt15{Iq?$n92;AK1NLaTh*-$`!ynjD6S#F>x|$f0LBPRe#@qE*?Kq4E_b za5%s4*xZ2nSW{Z%3FX<9m}dOs$VcSqzW@x-(7u4O*WuyHs}ro5a-S zw|>lYl<3}IZL>s9Xs*_pu!=g*nCqiEMoV^w^!EA7qcE?{x0kXX`~7y0$I$(sn65Kz|r~2 zuwrh6>D^ohHIpA;IhXjXV3+`#l9q)X)PjkO_ywh>q3ds_?539s0AJuXv?`#2W!~&i z?6)aha>52#$FW(C^?cWD?8n`^>Udc>8mk^Y!5K~B?N{`h(9i1ZZ9fy~adYbK3^qtL^E(PAH zUJ_k=4l3NeSt?-B6S%$m82!Tk;`&5TgfnpdzXDpmf70FmH=zB)sQ){l{llOC0QY|Z&kKz9Ep5rh6v_J39Q!_V&jN#cJawUoJs zxr4Lyzi9rYSE<*PTprGDK!%s!P%cnlC@UxrD0?U%6bOn9iu-i~Lb1N?-hXXu|Hxp2 zvV?Mma%6lp0Nk9xo|dmm#u#ipAU+Fj94_^MTL221~0s>UEK$jJfX4j-`?oyM zt26Oe_CK_IuY>zP$Ns&Z0Jp&F0RL+q5C6Ywc?7usEl+@t>vdZEt5$%I{~!H&xS88J zK->U-tpikWvT_E3{==5hbasBVX#Px=*SV=>`xf%&@vtjM0}X|Ftw20h5GxA^2y8CE zXU+qH2tX|PAr`OW0OGQ=;1m@$=ir7|nDcX6T5)ml^YNMUf%pVCIJsYMh0{vFii=Ow Z_;0Vo+|A>!XAk0dRRU;fWz=K={{x_g$L;_C literal 0 HcmV?d00001 From 6ad4721f6a1949668117c878c28c7788ac301ffa Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 27 Mar 2012 16:32:41 +0800 Subject: [PATCH 033/183] add: crengine as submodule --- .gitmodules | 3 +++ crengine | 1 + 2 files changed, 4 insertions(+) create mode 160000 crengine diff --git a/.gitmodules b/.gitmodules index 77f6eecd4..6c5c235cf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "djvulibre"] path = djvulibre url = git://djvu.git.sourceforge.net/gitroot/djvu/djvulibre.git +[submodule "crengine"] + path = crengine + url = git://crengine.git.sourceforge.net/gitroot/crengine/crengine diff --git a/crengine b/crengine new file mode 160000 index 000000000..e0a86d85a --- /dev/null +++ b/crengine @@ -0,0 +1 @@ +Subproject commit e0a86d85a3da3bed48a84fd3096ee6040ba1ba34 From 71c36064e2f46545012e583e7d069b1f43bc9e16 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Tue, 27 Mar 2012 18:31:41 +0200 Subject: [PATCH 034/183] added K3 left page keys for emulator on F3 and F4 --- keys.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/keys.lua b/keys.lua index 43e683120..489a93e05 100644 --- a/keys.lua +++ b/keys.lua @@ -123,6 +123,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 From d3318beb84b006f07fd10caf197439f4277a6f95 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Tue, 27 Mar 2012 19:54:07 +0200 Subject: [PATCH 035/183] print defined commands using dump --- unireader.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/unireader.lua b/unireader.lua index bf190fabd..d021cf267 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1175,6 +1175,5 @@ function UniReader:addAllCommands() end end) -- end panning - --print defined commands - --for k,v in pairs(self.commands.map) do print(v) end + print("## defined commands "..dump(self.commands.map)) end From 69a8ee83bbc3a2e03dad9c59f3e92e76ae126c91 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Tue, 27 Mar 2012 19:58:29 +0200 Subject: [PATCH 036/183] specify multiple keys for single command #55 This code still doesn't support different modifiers on keys, but I would rather refactor it in small steps first Current changes adds support for Kindle 3 left page keys reported by @houqp in issue #55 --- commands.lua | 11 +++++++++-- unireader.lua | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/commands.lua b/commands.lua index 43dfca87d..a6622ac43 100644 --- a/commands.lua +++ b/commands.lua @@ -62,8 +62,15 @@ Commands = { 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 diff --git a/unireader.lua b/unireader.lua index d021cf267..b2cb982a1 100644 --- a/unireader.lua +++ b/unireader.lua @@ -875,12 +875,12 @@ end -- command definitions function UniReader:addAllCommands() self.commands = Commands:new() - self.commands:add(KEY_PGFWD,nil,">", + self.commands:add({KEY_PGFWD,KEY_LPGFWD},nil,">", "next page", function(unireader) unireader:goto(unireader:nextView()) end) - self.commands:add(KEY_PGBCK,nil,"<", + self.commands:add({KEY_PGBCK,KEY_LPGBCK},nil,"<", "previous page", function(unireader) unireader:goto(unireader:prevView()) From 4821f10dda6161dadd65218eef88ab9d59152ad7 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 00:09:26 +0800 Subject: [PATCH 037/183] half done demo for crengine --- Makefile | 40 +++++++++++++++++++++++++++++++++------- filechooser.lua | 2 +- filesearcher.lua | 7 ++++++- kpdfview.c | 3 +++ reader.lua | 4 ++++ unireader.lua | 12 ++++++++---- 6 files changed, 55 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index ce64f0778..24f637bf3 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ MUPDFDIR=mupdf MUPDFTARGET=build/debug MUPDFLIBDIR=$(MUPDFDIR)/$(MUPDFTARGET) DJVUDIR=djvulibre +CRENGINEDIR=crengine FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.8 LFSDIR=luafilesystem @@ -55,16 +56,26 @@ 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 \ + $(CRENGINEDIR)/thirdparty/libjpeg/libjpeg.a \ + $(CRENGINEDIR)/thirdparty/zlib/libz.a \ + $(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)/libz.a + +# @TODO the libjpeg used by mupdf is too new for crengine and will cause +# a segment fault when decoding jpeg images in crengine, we need to fix +# this. 28.03 2012 (houqp) + #$(MUPDFLIBDIR)/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++ \ +kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) + $(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) -lstdc++ \ kpdfview.o \ einkfb.o \ pdf.o \ @@ -79,6 +90,8 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft $(LUALIB) \ djvu.o \ $(DJVULIBS) \ + cre.o \ + $(CRENGINELIBS) \ -o kpdfview einkfb.o input.o: %.o: %.c @@ -93,6 +106,9 @@ kpdfview.o pdf.o blitbuffer.o util.o drawcontext.o: %.o: %.c djvu.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ +cre.o: %.o: %.cpp + $(CC) -c -I$(CRENGINEDIR)/crengine/include/ $< -o $@ -lstdc++ + lfs.o: $(LFSDIR)/src/lfs.c $(CC) -c $(CFLAGS) -I$(LUADIR)/src -I$(LFSDIR)/src $(LFSDIR)/src/lfs.c -o $@ @@ -112,6 +128,7 @@ clean: cleanthirdparty: make -C $(LUADIR) clean make -C $(MUPDFDIR) clean + make -C $(CRENGINEDIR) clean -rm -rf $(DJVUDIR)/build -rm -f $(MUPDFDIR)/fontdump.host -rm -f $(MUPDFDIR)/cmapdump.host @@ -139,10 +156,19 @@ else endif make -C $(DJVUDIR)/build +$(CRENGINELIBS): + cd $(CRENGINEDIR) && cmake -D CR3_PNG=1 -D CR3_JPEG=1 . + cd $(CRENGINEDIR)/crengine && make + cd $(CRENGINEDIR)/thirdparty/libjpeg && make + cd $(CRENGINEDIR)/thirdparty/chmlib && make + cd $(CRENGINEDIR)/thirdparty/antiword && make + cd $(CRENGINEDIR)/thirdparty/libpng && make + cd $(CRENGINEDIR)/thirdparty/zlib && 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 diff --git a/filechooser.lua b/filechooser.lua index c4e2f1378..e1be925cb 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -54,7 +54,7 @@ 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 == "fb2" or file_type == "chm" then table.insert(self.files, f) end end diff --git a/filesearcher.lua b/filesearcher.lua index f2792c268..097fb5c57 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -30,10 +30,15 @@ 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 == "fb2" or file_type == "chm" then file_entry = {dir=d, name=f,} table.insert(self.files, file_entry) --print("file:"..d.."/"..f) diff --git a/kpdfview.c b/kpdfview.c index d14a38053..a09a9a0ab 100644 --- a/kpdfview.c +++ b/kpdfview.c @@ -26,6 +26,8 @@ #include "blitbuffer.h" #include "drawcontext.h" #include "pdf.h" +#include "djvu.h" +#include "cre.h" #include "einkfb.h" #include "input.h" #include "ft.h" @@ -53,6 +55,7 @@ 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); diff --git a/reader.lua b/reader.lua index 0ad9da911..1d4e7bb6e 100755 --- a/reader.lua +++ b/reader.lua @@ -20,6 +20,7 @@ require "alt_getopt" require "pdfreader" require "djvureader" +require "crereader" require "filechooser" require "settings" require "screen" @@ -42,6 +43,8 @@ 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 == "fb2" or file_type == "chm" then + reader = CREReader end if reader then local ok, err = reader:open(filename) @@ -133,6 +136,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") diff --git a/unireader.lua b/unireader.lua index bf956be15..56f96fbf2 100644 --- a/unireader.lua +++ b/unireader.lua @@ -493,7 +493,7 @@ function UniReader:addJump(pageno, notes) local notes_to_add = notes if not notes_to_add then -- no notes given, auto generate from TOC entry - notes_to_add = self:getTOCTitleByPage(self.pageno) + notes_to_add = self:getTocTitleByPage(self.pageno) if notes_to_add ~= "" then notes_to_add = "in "..notes_to_add end @@ -563,6 +563,10 @@ function UniReader:goto(no) end end +function UniReader:redrawCurrentPage() + self:goto(self.pageno) +end + function UniReader:nextView() local pageno = self.pageno @@ -659,7 +663,7 @@ function UniReader:fillTOC() self.toc = self.doc:getTOC() end -function UniReader:getTOCTitleByPage(pageno) +function UniReader:getTocTitleByPage(pageno) if not self.toc then -- build toc when needed. self:fillTOC() @@ -734,7 +738,7 @@ function UniReader:showMenu() ypos = ypos + 15 local face, fhash = Font:getFaceAndHash(22) - local cur_section = self:getTOCTitleByPage(self.pageno) + local cur_section = self:getTocTitleByPage(self.pageno) if cur_section ~= "" then cur_section = "Section: "..cur_section end @@ -1010,7 +1014,7 @@ function UniReader:addAllCommands() "open menu", function(unireader) unireader:showMenu() - unireader:goto(unireader.pageno) + unireader:redrawCurrentPage() end) -- panning local panning_keys = {Keydef:new(KEY_FW_LEFT,MOD_ANY),Keydef:new(KEY_FW_RIGHT,MOD_ANY),Keydef:new(KEY_FW_UP,MOD_ANY),Keydef:new(KEY_FW_DOWN,MOD_ANY),Keydef:new(KEY_FW_PRESS,MOD_ANY)} From c56516d5d148ca780ebaffa059b98463c58e9ac4 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 00:16:00 +0800 Subject: [PATCH 038/183] add: data from crengine --- Makefile | 1 + cre.cpp | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++ cre.h | 28 +++++++ crereader.lua | 57 +++++++++++++++ 4 files changed, 285 insertions(+) create mode 100644 cre.cpp create mode 100644 cre.h create mode 100644 crereader.lua diff --git a/Makefile b/Makefile index 24f637bf3..5744031ce 100644 --- a/Makefile +++ b/Makefile @@ -164,6 +164,7 @@ $(CRENGINELIBS): cd $(CRENGINEDIR)/thirdparty/antiword && make cd $(CRENGINEDIR)/thirdparty/libpng && make cd $(CRENGINEDIR)/thirdparty/zlib && make + 'cp' -ru $(CRENGINELIBS)/cr3gui/data ./ $(LUALIB): make -C lua/src CC="$(CC)" CFLAGS="$(CFLAGS)" MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-Wl,-E" liblua.a diff --git a/cre.cpp b/cre.cpp new file mode 100644 index 000000000..cce2c8884 --- /dev/null +++ b/cre.cpp @@ -0,0 +1,199 @@ +/* + KindlePDFViewer: CREngine abstraction for Lua + Copyright (C) 2012 Hans-Werner Hilse + Qingping Hou + + 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 . +*/ + + +extern "C" { +#include "blitbuffer.h" +#include "drawcontext.h" +#include "cre.h" +} + +#include "crengine.h" + +//using namespace std; + +typedef struct CreDocument { + LVDocView *text_view; +} 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); + + CreDocument *doc = (CreDocument*) lua_newuserdata(L, sizeof(CreDocument)); + luaL_getmetatable(L, "credocument"); + lua_setmetatable(L, -2); + + doc->text_view = new LVDocView(); + doc->text_view->setStyleSheet(lString8(style_sheet)); + doc->text_view->LoadDocument(file_name); + doc->text_view->setViewMode(DVM_SCROLL, -1); + doc->text_view->Resize(width, height); + doc->text_view->Render(); + + return 1; +} + +static int closeDocument(lua_State *L) { + CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); + delete doc->text_view; + + 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 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 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 getPos(lua_State *L) { + CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); + + lua_pushinteger(L, doc->text_view->GetPos()); + + 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; +} + +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; + LVGrayDrawBuf drawBuf(w, h, 8); + + doc->text_view->Resize(w, h); + doc->text_view->Render(); + drawBuf.Clear(0xFFFFFF); + 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++) { + bbptr[x] = 255 - (((pmptr[x*2 + 1] & 0xF0) >> 4) | + (pmptr[x*2] & 0xF0)); + } + if(bb->w & 1) { + bbptr[x] = 255 - (pmptr[x*2] & 0xF0); + } + bbptr += bb->pitch; + pmptr += w; + } +} + + +static const struct luaL_Reg cre_func[] = { + {"openDocument", openDocument}, + {NULL, NULL} +}; + +static const struct luaL_Reg credocument_meth[] = { + {"getPages", getNumberOfPages}, + {"getCurrentPage", getCurrentPage}, + {"getPos", getPos}, + {"GetFullHeight", getFullHeight}, + {"gotoPos", gotoPos}, + {"gotoPage", gotoPage}, + {"drawCurrentPage", drawCurrentPage}, + //{"getTOC", getTableOfContent}, + {"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 fonts for CREngine */ + InitFontManager(lString8("./fonts")); + + lString8 fontDir("./fonts"); + LVContainerRef dir = LVOpenDirectory( LocalToUnicode(fontDir).c_str() ); + if ( !dir.isNull() ) + for ( int i=0; iGetObjectCount(); i++ ) { + const LVContainerItemInfo * item = dir->GetObjectInfo(i); + lString16 fileName = item->GetName(); + if ( !item->IsContainer() && fileName.length()>4 && lString16(fileName, fileName.length()-4, 4)==L".ttf" ) { + lString8 fn = UnicodeToLocal(fileName); + printf("loading font: %s\n", fn.c_str()); + if ( !fontMan->RegisterFont(fn) ) { + printf(" failed\n"); + } + } + } + +#ifdef DEBUG_CRENGINE + CRLog::setStdoutLogger(); + CRLog::setLogLevel(CRLog::LL_DEBUG); +#endif + + return 1; +} diff --git a/cre.h b/cre.h new file mode 100644 index 000000000..f4166095e --- /dev/null +++ b/cre.h @@ -0,0 +1,28 @@ +/* + KindlePDFViewer: CREngine abstraction for Lua + Copyright (C) 2012 Hans-Werner Hilse + Qingping Hou + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef _CRENGING_H +#define _CRENGING_H + +#include +#include +#include + +int luaopen_cre(lua_State *L); +#endif diff --git a/crereader.lua b/crereader.lua new file mode 100644 index 000000000..eac8ebe37 --- /dev/null +++ b/crereader.lua @@ -0,0 +1,57 @@ +require "unireader" +require "inputbox" + +CREReader = UniReader:new{ + pos = 0, + pan_overlap_vertical = 0, +} + +-- open a CREngine supported file and its settings store +function CREReader:open(filename) + local ok + local file_type = string.lower(string.match(filename, ".+%.([^.]+)")) + local style_sheet = "./data/"..file_type..".css" + ok, self.doc = pcall(cre.openDocument, filename, style_sheet, + width, height) + if not ok then + return false, self.doc -- will contain error message + end + + return true +end + +function CREReader:goto(pos) + local pos = math.min(pos, self.doc:GetFullHeight()) + pos = math.max(pos, 0) + self.doc:gotoPos(pos) + self.doc:drawCurrentPage(self.nulldc, fb.bb) + + if self.rcount == self.rcountmax then + print("full refresh") + self.rcount = 1 + fb:refresh(0) + else + print("partial refresh") + self.rcount = self.rcount + 1 + fb:refresh(1) + end + + self.pos = pos + self.pageno = self.doc:getCurrentPage() +end + +function CREReader:redrawCurrentPage() + self:goto(self.pos) +end + +function CREReader:getTocTitleByPage(page) + return "" +end + +function CREReader:nextView() + return self.pos + height - self.pan_overlap_vertical +end + +function CREReader:prevView() + return self.pos - height + self.pan_overlap_vertical +end From 8bd1cb09ea804f5b870885476bf77ce88174b5ce Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Wed, 28 Mar 2012 19:47:10 +0200 Subject: [PATCH 039/183] lua.h dependecy #77 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5744031ce..a84eb7f84 100644 --- a/Makefile +++ b/Makefile @@ -107,7 +107,7 @@ djvu.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ cre.o: %.o: %.cpp - $(CC) -c -I$(CRENGINEDIR)/crengine/include/ $< -o $@ -lstdc++ + $(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 $@ From ee5b32aced9e59c385335be09348087474937b0b Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Wed, 28 Mar 2012 20:02:13 +0200 Subject: [PATCH 040/183] build crengine after depenencies #77 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a84eb7f84..369b1e09e 100644 --- a/Makefile +++ b/Makefile @@ -158,12 +158,12 @@ endif $(CRENGINELIBS): cd $(CRENGINEDIR) && cmake -D CR3_PNG=1 -D CR3_JPEG=1 . - cd $(CRENGINEDIR)/crengine && make cd $(CRENGINEDIR)/thirdparty/libjpeg && make cd $(CRENGINEDIR)/thirdparty/chmlib && make cd $(CRENGINEDIR)/thirdparty/antiword && make cd $(CRENGINEDIR)/thirdparty/libpng && make cd $(CRENGINEDIR)/thirdparty/zlib && make + cd $(CRENGINEDIR)/crengine && make 'cp' -ru $(CRENGINELIBS)/cr3gui/data ./ $(LUALIB): From e97cfb91e79480bb9b735bfdef4e16d6dda0f3e1 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Thu, 29 Mar 2012 00:20:07 +0200 Subject: [PATCH 041/183] hack crsetup.h using grep #77 --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 369b1e09e..264c26eb9 100644 --- a/Makefile +++ b/Makefile @@ -113,10 +113,12 @@ 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 git submodule init git submodule update + 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 $(CRENGINEDIR)/thirdparty/zlib/qconfig.h || touch $(CRENGINEDIR)/thirdparty/zlib/qconfig.h test -f mupdf-thirdparty.zip || wget http://www.mupdf.com/download/mupdf-thirdparty.zip unzip mupdf-thirdparty.zip -d mupdf test -f lua-5.1.4.tar.gz || wget http://www.lua.org/ftp/lua-5.1.4.tar.gz @@ -164,7 +166,6 @@ $(CRENGINELIBS): cd $(CRENGINEDIR)/thirdparty/libpng && make cd $(CRENGINEDIR)/thirdparty/zlib && make cd $(CRENGINEDIR)/crengine && make - 'cp' -ru $(CRENGINELIBS)/cr3gui/data ./ $(LUALIB): make -C lua/src CC="$(CC)" CFLAGS="$(CFLAGS)" MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-Wl,-E" liblua.a From 178e6597e218ba05dbdef6002f5dc4a947e561ba Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 17:49:59 +0800 Subject: [PATCH 042/183] add: TOC support --- cre.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/cre.cpp b/cre.cpp index cce2c8884..b7aa36f4a 100644 --- a/cre.cpp +++ b/cre.cpp @@ -110,6 +110,66 @@ static int getFullHeight(lua_State *L) { 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->getY()); + 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, depth=1, title="chapter1"}, + * {page=54, depth=1, title="chapter2"}, + * } + * + * Warnning: not like pdf or djvu support, page here refers to the + * position(height) within the document, not the real page number. + * + */ +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; +} + static int drawCurrentPage(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); @@ -153,10 +213,10 @@ static const struct luaL_Reg credocument_meth[] = { {"getCurrentPage", getCurrentPage}, {"getPos", getPos}, {"GetFullHeight", getFullHeight}, + {"getTOC", getTableOfContent}, {"gotoPos", gotoPos}, {"gotoPage", gotoPage}, {"drawCurrentPage", drawCurrentPage}, - //{"getTOC", getTableOfContent}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} From ad9d13a94738c3692856a1f9f2732a45e57a4c52 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 18:17:32 +0800 Subject: [PATCH 043/183] mod: rename TOC to Toc, adapt showMenu to CREReader --- cre.cpp | 2 +- crereader.lua | 26 ++++++++++++++++++++++++-- djvu.c | 2 +- pdf.c | 2 +- unireader.lua | 30 ++++++++++++++++++------------ 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/cre.cpp b/cre.cpp index b7aa36f4a..98cbe286a 100644 --- a/cre.cpp +++ b/cre.cpp @@ -213,7 +213,7 @@ static const struct luaL_Reg credocument_meth[] = { {"getCurrentPage", getCurrentPage}, {"getPos", getPos}, {"GetFullHeight", getFullHeight}, - {"getTOC", getTableOfContent}, + {"getToc", getTableOfContent}, {"gotoPos", gotoPos}, {"gotoPage", gotoPage}, {"drawCurrentPage", drawCurrentPage}, diff --git a/crereader.lua b/crereader.lua index eac8ebe37..04548345e 100644 --- a/crereader.lua +++ b/crereader.lua @@ -20,6 +20,10 @@ function CREReader:open(filename) return true end +function CREReader:setzoom(page, preCache) + return +end + function CREReader:goto(pos) local pos = math.min(pos, self.doc:GetFullHeight()) pos = math.max(pos, 0) @@ -44,8 +48,26 @@ function CREReader:redrawCurrentPage() self:goto(self.pos) end -function CREReader:getTocTitleByPage(page) - return "" +-- used in UniReader:showMenu() +function UniReader:_drawReadingInfo() + local ypos = height - 50 + local load_percent = (self.pos / self.doc:GetFullHeight()) + + fb.bb:paintRect(0, ypos, width, 50, 0) + + ypos = ypos + 15 + local face, fhash = Font:getFaceAndHash(22) + local cur_section = self:getTocTitleByPage(self.pos) + if cur_section ~= "" then + cur_section = "Section: "..cur_section + end + renderUtf8Text(fb.bb, 10, ypos+6, face, fhash, + "Position: "..math.floor((load_percent*100)).."%".. + " "..cur_section, true) + + ypos = ypos + 15 + blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, + 5, 4, load_percent, 8) end function CREReader:nextView() diff --git a/djvu.c b/djvu.c index 6bcac2290..5367efb0e 100644 --- a/djvu.c +++ b/djvu.c @@ -455,7 +455,7 @@ 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}, {"__gc", closeDocument}, diff --git a/pdf.c b/pdf.c index b335d65c7..53efff9ba 100644 --- a/pdf.c +++ b/pdf.c @@ -304,7 +304,7 @@ static const struct luaL_Reg pdfdocument_meth[] = { {"authenticatePassword", authenticatePassword}, {"openPage", openPage}, {"getPages", getNumberOfPages}, - {"getTOC", getTableOfContent}, + {"getToc", getTableOfContent}, {"close", closeDocument}, {"__gc", closeDocument}, {NULL, NULL} diff --git a/unireader.lua b/unireader.lua index e6049e1ea..96e833306 100644 --- a/unireader.lua +++ b/unireader.lua @@ -528,7 +528,7 @@ function UniReader:addJump(pageno, notes) local jump_item = nil local notes_to_add = notes if not notes_to_add then - -- no notes given, auto generate from TOC entry + -- no notes given, auto generate from Toc entry notes_to_add = self:getTocTitleByPage(self.pageno) if notes_to_add ~= "" then notes_to_add = "in "..notes_to_add @@ -541,7 +541,7 @@ function UniReader:addJump(pageno, notes) table.remove(self.jump_stack, _t) -- if original notes is not empty, probably defined by users, -- we use the original notes to overwrite auto generated notes - -- from TOC entry + -- from Toc entry if jump_item.notes ~= "" then notes_to_add = jump_item.notes end @@ -691,18 +691,18 @@ function UniReader:screenRotate(orien) self:goto(self.pageno) end -function UniReader:cleanUpTOCTitle(title) +function UniReader:cleanUpTocTitle(title) return title:gsub("\13", "") end -function UniReader:fillTOC() - self.toc = self.doc:getTOC() +function UniReader:fillToc() + self.toc = self.doc:getToc() end function UniReader:getTocTitleByPage(pageno) if not self.toc then -- build toc when needed. - self:fillTOC() + self:fillToc() end -- no table of content @@ -717,20 +717,20 @@ function UniReader:getTocTitleByPage(pageno) end pre_entry = _v end - return self:cleanUpTOCTitle(pre_entry.title) + return self:cleanUpTocTitle(pre_entry.title) end -function UniReader:showTOC() +function UniReader:showToc() if not self.toc then -- build toc when needed. - self:fillTOC() + self:fillToc() end local menu_items = {} local filtered_toc = {} -- build menu items for k,v in ipairs(self.toc) do table.insert(menu_items, - (" "):rep(v.depth-1)..self:cleanUpTOCTitle(v.title)) + (" "):rep(v.depth-1)..self:cleanUpTocTitle(v.title)) table.insert(filtered_toc,v.page) end toc_menu = SelectMenu:new{ @@ -789,7 +789,8 @@ function UniReader:showHighLight() end end -function UniReader:showMenu() +-- used in UniReader:showMenu() +function UniReader:_drawReadingInfo() local ypos = height - 50 local load_percent = (self.pageno / self.doc:getPages()) @@ -808,6 +809,11 @@ function UniReader:showMenu() ypos = ypos + 15 blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, 5, 4, load_percent, 8) +end + +function UniReader:showMenu() + self:_drawReadingInfo() + fb:refresh(1) while 1 do local ev = input.waitForEvent() @@ -1004,7 +1010,7 @@ function UniReader:addAllCommands() self.commands:add(KEY_T,nil,"T", "show table of content", function(unireader) - unireader:showTOC() + unireader:showToc() end) self.commands:add(KEY_B,nil,"B", "show jump stack", From 28e53ce7718e3efa7880af4dc5e8f5100f205a68 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 19:35:56 +0800 Subject: [PATCH 044/183] mod: adapt jumpstack in CREReader --- crereader.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crereader.lua b/crereader.lua index 04548345e..832bdf5b3 100644 --- a/crereader.lua +++ b/crereader.lua @@ -27,6 +27,12 @@ end function CREReader:goto(pos) local pos = math.min(pos, self.doc:GetFullHeight()) pos = math.max(pos, 0) + + -- add to jump_stack, distinguish jump from normal page turn + if self.pos and math.abs(self.pos - pos) > height then + self:addJump(self.pos) + end + self.doc:gotoPos(pos) self.doc:drawCurrentPage(self.nulldc, fb.bb) From 7e07ea5a1f85c072e0283783db200231168667dd Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 20:52:57 +0800 Subject: [PATCH 045/183] fix: bug in Commands:new() assign a new map to every created object --- commands.lua | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/commands.lua b/commands.lua index a6622ac43..731f3deaa 100644 --- a/commands.lua +++ b/commands.lua @@ -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 and 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 "") end @@ -61,6 +69,7 @@ Commands = { map = {}, size = 0 } + function Commands:add(keycode,modifier,keydescr,help,func) if type(keycode) == "table" then for i=1,#keycode,1 do @@ -72,11 +81,13 @@ function Commands:add(keycode,modifier,keydescr,help,func) 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 + 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) @@ -95,25 +106,32 @@ 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) + return obj end From e94fe052d361b908038069100c7c4b83058f0ed1 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 21:10:25 +0800 Subject: [PATCH 046/183] mod: rewrite selectmenu with commands also fixed bug in page counting --- selectmenu.lua | 285 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 187 insertions(+), 98 deletions(-) diff --git a/selectmenu.lua b/selectmenu.lua index 3572a5c99..b157bb301 100644 --- a/selectmenu.lua +++ b/selectmenu.lua @@ -2,6 +2,7 @@ require "rendertext" require "keys" require "graphics" require "font" +require "commands" SelectMenu = { -- font for displaying item names @@ -32,10 +33,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 +51,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,43 +69,154 @@ 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 + 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 + 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_E = {} + for i = KEY_Q, KEY_P do + table.insert(KEY_Q_to_E, Keydef:new(i, nil, "")) + end + self.commands:addGroup("Q to E", KEY_Q_to_E, + "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 + +------------------------------------------------ +-- 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 @@ -106,8 +224,8 @@ function SelectMenu:choose(ypos, height) local tface, tfhash = Font:getFaceAndHash(25, Font.tfont) local fface, ffhash = Font:getFaceAndHash(16, Font.ffont) - 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) @@ -125,11 +243,11 @@ function SelectMenu:choose(ypos, height) y = y + self.spacing renderUtf8Text(fb.bb, 30, y, cface, cfhash, self.no_item_msg, true) - markerdirty = false + self.markerdirty = false 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) @@ -153,19 +271,21 @@ function SelectMenu:choose(ypos, height) renderUtf8Text(fb.bb, 50, y, cface, cfhash, 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) + "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 +295,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() 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 + end + end -- EOF if + end -- EOF while + return nil end From c89e3efc19e85c7ae4680bc8e7330ee71b5e4958 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 29 Mar 2012 21:16:09 +0800 Subject: [PATCH 047/183] fix: clear commands when no item in menu --- selectmenu.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/selectmenu.lua b/selectmenu.lua index b157bb301..c4ce67f45 100644 --- a/selectmenu.lua +++ b/selectmenu.lua @@ -210,6 +210,16 @@ function SelectMenu:addAllCommands() 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 ------------------------------------------------ @@ -244,6 +254,7 @@ function SelectMenu:choose(ypos, height) renderUtf8Text(fb.bb, 30, y, cface, cfhash, self.no_item_msg, true) self.markerdirty = false + self:clearCommands() else local c for c = 1, self.perpage do From 5c4b0868bae42ccef7bd35be4fc445ee61f6ad1c Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 12:01:59 +0800 Subject: [PATCH 048/183] add: del method in commands --- commands.lua | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/commands.lua b/commands.lua index 731f3deaa..2b5b58aef 100644 --- a/commands.lua +++ b/commands.lua @@ -88,6 +88,24 @@ function Commands:addGroup(keygroup,keys,help,func) end end +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:_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) From c0edbfae27934cdbd108cf6319d106270b9b5a8f Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 12:06:33 +0800 Subject: [PATCH 049/183] mod: remove not used shorcuts in CREReader --- crereader.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crereader.lua b/crereader.lua index 832bdf5b3..4862000f2 100644 --- a/crereader.lua +++ b/crereader.lua @@ -6,6 +6,10 @@ CREReader = UniReader:new{ pan_overlap_vertical = 0, } +function CREReader:init() + self:adjustCreReaderCommands() +end + -- open a CREngine supported file and its settings store function CREReader:open(filename) local ok @@ -83,3 +87,24 @@ end function CREReader:prevView() return self.pos - height + self.pan_overlap_vertical end + +function CREReader:adjustCreReaderCommands() + self.commands:del(KEY_G, nil, "G") + self.commands:del(KEY_J, nil, "J") + self.commands:del(KEY_K, nil, "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, nil, "F") + self.commands:del(KEY_F, MOD_SHIFT, "F") + self.commands:del(KEY_F, MOD_ALT, "F") +end From ad750a89f8f9d9e9317da08f0e4e5cba664864fa Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 12:31:09 +0800 Subject: [PATCH 050/183] add: font zooming in CREReader --- cre.cpp | 50 +++++++++++++++++++++++++++++++------------------- crereader.lua | 17 +++++++++++++++++ 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/cre.cpp b/cre.cpp index 98cbe286a..96c2aad0a 100644 --- a/cre.cpp +++ b/cre.cpp @@ -60,24 +60,6 @@ static int closeDocument(lua_State *L) { 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 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 getNumberOfPages(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); @@ -170,6 +152,35 @@ static int getTableOfContent(lua_State *L) { return 1; } +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 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; +} + +/* 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 drawCurrentPage(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); @@ -214,8 +225,9 @@ static const struct luaL_Reg credocument_meth[] = { {"getPos", getPos}, {"GetFullHeight", getFullHeight}, {"getToc", getTableOfContent}, - {"gotoPos", gotoPos}, {"gotoPage", gotoPage}, + {"gotoPos", gotoPos}, + {"zoomFont", zoomFont}, {"drawCurrentPage", drawCurrentPage}, {"close", closeDocument}, {"__gc", closeDocument}, diff --git a/crereader.lua b/crereader.lua index 4862000f2..f9e60bd86 100644 --- a/crereader.lua +++ b/crereader.lua @@ -89,6 +89,7 @@ function CREReader:prevView() end function CREReader:adjustCreReaderCommands() + -- delete commands self.commands:del(KEY_G, nil, "G") self.commands:del(KEY_J, nil, "J") self.commands:del(KEY_K, nil, "K") @@ -107,4 +108,20 @@ function CREReader:adjustCreReaderCommands() self.commands:del(KEY_F, nil, "F") self.commands:del(KEY_F, MOD_SHIFT, "F") self.commands:del(KEY_F, MOD_ALT, "F") + + -- overwrite commands + self.commands:add(KEY_PGFWD, MOD_SHIFT_OR_ALT, ">", + "increase font size", + function(cr) + cr.doc:zoomFont(1) + cr:redrawCurrentPage() + end + ) + self.commands:add(KEY_PGBCK, MOD_SHIFT_OR_ALT, "<", + "decrease font size", + function(cr) + cr.doc:zoomFont(-1) + cr:redrawCurrentPage() + end + ) end From 612890850cab20264ea9002e12b1669f74b317dd Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 12:38:41 +0800 Subject: [PATCH 051/183] add: percent jump in CREReader --- crereader.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crereader.lua b/crereader.lua index f9e60bd86..3fd4b091d 100644 --- a/crereader.lua +++ b/crereader.lua @@ -124,4 +124,17 @@ function CREReader:adjustCreReaderCommands() 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 *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 + ) end From 8a2b86e2032b70780bbdd8d436b318f5cca634e8 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 13:07:48 +0800 Subject: [PATCH 052/183] mod: factor last reading page saving and resotring --- reader.lua | 1 + unireader.lua | 41 ++++++++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/reader.lua b/reader.lua index 8e6b33af6..087dc72a2 100755 --- a/reader.lua +++ b/reader.lua @@ -51,6 +51,7 @@ function openFile(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() diff --git a/unireader.lua b/unireader.lua index 81b37e1fc..51c02c5cf 100644 --- a/unireader.lua +++ b/unireader.lua @@ -128,6 +128,24 @@ end --[ following are default methods ]-- +function UniReader:initGlobalSettings(settings) + local pan_overlap_vertical = settings:readSetting("pan_overlap_vertical") + if pan_overlap_vertical then + self.pan_overlap_vertical = pan_overlap_vertical + end + -- initialize commands + self:addAllCommands() + + local cache_max_memsize = settings:readSetting("cache_max_memsize") + if cache_max_memsize then + self.cache_max_memsize = cache_max_memsize + end + + local cache_max_ttl = settings:readSetting("cache_max_ttl") + if cache_max_ttl then + self.cache_max_ttl = cache_max_ttl + end +end function UniReader:loadSettings(filename) if self.doc ~= nil then @@ -156,23 +174,12 @@ function UniReader:loadSettings(filename) return false end -function UniReader:initGlobalSettings(settings) - local pan_overlap_vertical = settings:readSetting("pan_overlap_vertical") - if pan_overlap_vertical then - self.pan_overlap_vertical = pan_overlap_vertical - end - -- initialize commands - self:addAllCommands() - - local cache_max_memsize = settings:readSetting("cache_max_memsize") - if cache_max_memsize then - self.cache_max_memsize = cache_max_memsize - end +function UniReader:getLastPageOrPos() + return self.settings:readSetting("last_page") or 1 +end - local cache_max_ttl = settings:readSetting("cache_max_ttl") - if cache_max_ttl then - self.cache_max_ttl = cache_max_ttl - end +function UniReader:saveLastPageOrPos() + self.settings:savesetting("last_page", self.pageno) end -- guarantee that we have enough memory in cache @@ -869,7 +876,7 @@ function UniReader:inputLoop() self.doc:close() end if self.settings ~= nil then - self.settings:savesetting("last_page", self.pageno) + self:saveLastPageOrPos() self.settings:savesetting("gamma", self.globalgamma) self.settings:savesetting("jumpstack", self.jump_stack) self.settings:savesetting("bbox", self.bbox) From 937f445b920efdc812d17098c1bdead4ec1eeb46 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 14:00:41 +0800 Subject: [PATCH 053/183] fix: mistakenly over written unireader's method --- crereader.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crereader.lua b/crereader.lua index 3fd4b091d..21826b202 100644 --- a/crereader.lua +++ b/crereader.lua @@ -58,8 +58,8 @@ function CREReader:redrawCurrentPage() self:goto(self.pos) end --- used in UniReader:showMenu() -function UniReader:_drawReadingInfo() +-- used in CREReader:showMenu() +function CREReader:_drawReadingInfo() local ypos = height - 50 local load_percent = (self.pos / self.doc:GetFullHeight()) From 37dfc84d5ae69f77a120160e49d5de99553314b2 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 14:10:04 +0800 Subject: [PATCH 054/183] fix: move commands initilization from global init to local If we call addAllCommands in initGlobalSettings, all the readers will share the same commands and we cannot adjust commands for a specific reader. I thus moved this method call to Unireader:init() method. --- crereader.lua | 1 + reader.lua | 1 - unireader.lua | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crereader.lua b/crereader.lua index 21826b202..f6081ecb0 100644 --- a/crereader.lua +++ b/crereader.lua @@ -7,6 +7,7 @@ CREReader = UniReader:new{ } function CREReader:init() + self:addAllCommands() self:adjustCreReaderCommands() end diff --git a/reader.lua b/reader.lua index 087dc72a2..adbd99447 100755 --- a/reader.lua +++ b/reader.lua @@ -50,7 +50,6 @@ function openFile(filename) 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) diff --git a/unireader.lua b/unireader.lua index 51c02c5cf..a45776042 100644 --- a/unireader.lua +++ b/unireader.lua @@ -101,6 +101,8 @@ end -- overwrite other methods if needed. ---------------------------------------------------- function UniReader:init() + -- initialize commands + self:addAllCommands() end -- open a file and its settings store @@ -133,8 +135,6 @@ function UniReader:initGlobalSettings(settings) if pan_overlap_vertical then self.pan_overlap_vertical = pan_overlap_vertical end - -- initialize commands - self:addAllCommands() local cache_max_memsize = settings:readSetting("cache_max_memsize") if cache_max_memsize then From 9df574de01d065d26dc7897888df27856d8f093d Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 30 Mar 2012 14:25:36 +0800 Subject: [PATCH 055/183] add: last reading position saving and restoring --- crereader.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crereader.lua b/crereader.lua index f6081ecb0..679df6d4a 100644 --- a/crereader.lua +++ b/crereader.lua @@ -25,10 +25,21 @@ function CREReader:open(filename) return true end +function CREReader:getLastPageOrPos() + return self.settings:readSetting("last_pos") or 0 +end + +function CREReader:saveLastPageOrPos() + self.settings:savesetting("last_pos", self.pos) +end + function CREReader:setzoom(page, preCache) return end +function CREReader:addJump(pos, notes) +end + function CREReader:goto(pos) local pos = math.min(pos, self.doc:GetFullHeight()) pos = math.max(pos, 0) From 92ec4213c09cfc0d8b4815459c4173deb88ab23c Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 12:53:40 +0200 Subject: [PATCH 056/183] show djvu cache size #80 Currently it shows cache_max_memsize and cache_item_max_pixels from lua and djvu cache size on menu key --- djvu.c | 9 +++++++++ unireader.lua | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/djvu.c b/djvu.c index 6bcac2290..b4296b560 100644 --- a/djvu.c +++ b/djvu.c @@ -447,6 +447,14 @@ 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 const struct luaL_Reg djvu_func[] = { {"openDocument", openDocument}, {NULL, NULL} @@ -458,6 +466,7 @@ static const struct luaL_Reg djvudocument_meth[] = { {"getTOC", getTableOfContent}, {"getPageText", getPageText}, {"close", closeDocument}, + {"getCacheSize", getCacheSize}, {"__gc", closeDocument}, {NULL, NULL} }; diff --git a/unireader.lua b/unireader.lua index b2cb982a1..a13a2ab7f 100644 --- a/unireader.lua +++ b/unireader.lua @@ -126,6 +126,13 @@ function UniReader:toggleTextHighLight(word_list) return end +---------------------------------------------------- +-- renderer memory +---------------------------------------------------- + +function UniReader:getCacheSize() + return -1 +end --[ following are default methods ]-- @@ -799,7 +806,7 @@ function UniReader:showMenu() end renderUtf8Text(fb.bb, 10, ypos+6, face, fhash, "Page: "..self.pageno.."/"..self.doc:getPages().. - " "..cur_section, true) + " "..cur_section.." Memory: "..( self.cache_max_memsize / 1024 ).." "..( self.cache_item_max_pixels / 1024 ).." "..( self.doc:getCacheSize() / 1024 ).." k", true) ypos = ypos + 15 blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, From 91bd1277d7c3da88a3faacb7928d0d57032ad9aa Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 13:23:51 +0200 Subject: [PATCH 057/183] control renderer memory usage #80 added setCacheSize and cache_document_size configuration vairable --- djvu.c | 9 +++++++++ unireader.lua | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/djvu.c b/djvu.c index b4296b560..5f8d7935b 100644 --- a/djvu.c +++ b/djvu.c @@ -455,6 +455,14 @@ static int getCacheSize(lua_State *L) { return 1; } +static int setCacheSize(lua_State *L) { + DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); + int size = luaL_checkint(L, 2); + printf("ddjvu_cache_set_size = %d\n", size); + ddjvu_cache_set_size(doc->context, size); + return 0; +} + static const struct luaL_Reg djvu_func[] = { {"openDocument", openDocument}, {NULL, NULL} @@ -467,6 +475,7 @@ static const struct luaL_Reg djvudocument_meth[] = { {"getPageText", getPageText}, {"close", closeDocument}, {"getCacheSize", getCacheSize}, + {"setCacheSize", setCacheSize}, {"__gc", closeDocument}, {NULL, NULL} }; diff --git a/unireader.lua b/unireader.lua index a13a2ab7f..7ad8a6a6f 100644 --- a/unireader.lua +++ b/unireader.lua @@ -72,6 +72,8 @@ UniReader = { -- tile cache state: cache_current_memsize = 0, cache = {}, + -- renderer cache size + cache_document_size = 1024*1024*8, -- FIXME random, needs testing pagehash = nil, @@ -134,6 +136,11 @@ function UniReader:getCacheSize() return -1 end +function UniReader:setCacheSize(size) + return +end + + --[ following are default methods ]-- function UniReader:loadSettings(filename) @@ -158,6 +165,8 @@ function UniReader:loadSettings(filename) self.globalzoom = self.settings:readSetting("globalzoom") or 1.0 self.globalzoommode = self.settings:readSetting("globalzoommode") or -1 + self.doc:setCacheSize( self.cache_document_size ) + return true end return false From 60b7ccb3cb389fe266796f7ca0a766752cf047e2 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 13:38:33 +0200 Subject: [PATCH 058/183] added cleanCache bound to C key in menu #80 --- djvu.c | 12 ++++++++++-- unireader.lua | 6 ++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/djvu.c b/djvu.c index 5f8d7935b..d60107920 100644 --- a/djvu.c +++ b/djvu.c @@ -450,7 +450,7 @@ static int drawPage(lua_State *L) { 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); + printf("## ddjvu_cache_get_size = %d\n", size); lua_pushnumber(L, size); return 1; } @@ -458,11 +458,18 @@ static int getCacheSize(lua_State *L) { static int setCacheSize(lua_State *L) { DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); int size = luaL_checkint(L, 2); - printf("ddjvu_cache_set_size = %d\n", size); + printf("## ddjvu_cache_set_size = %d\n", size); ddjvu_cache_set_size(doc->context, size); return 0; } +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} @@ -476,6 +483,7 @@ static const struct luaL_Reg djvudocument_meth[] = { {"close", closeDocument}, {"getCacheSize", getCacheSize}, {"setCacheSize", setCacheSize}, + {"cleanCache", cleanCache}, {"__gc", closeDocument}, {NULL, NULL} }; diff --git a/unireader.lua b/unireader.lua index 7ad8a6a6f..e23389aa4 100644 --- a/unireader.lua +++ b/unireader.lua @@ -140,6 +140,10 @@ function UniReader:setCacheSize(size) return end +function UniReader:cleanCache() + return +end + --[ following are default methods ]-- @@ -827,6 +831,8 @@ function UniReader:showMenu() if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then if ev.code == KEY_BACK or ev.code == KEY_MENU then return + elseif ev.code == KEY_C then + self.doc:cleanCache() end end end From 4f0b41ebc500abb52dfb3c657f6d0857ef5ce473 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 13:57:35 +0200 Subject: [PATCH 059/183] show cache_current_memsize --- unireader.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/unireader.lua b/unireader.lua index e23389aa4..d4b163c84 100644 --- a/unireader.lua +++ b/unireader.lua @@ -819,7 +819,11 @@ function UniReader:showMenu() end renderUtf8Text(fb.bb, 10, ypos+6, face, fhash, "Page: "..self.pageno.."/"..self.doc:getPages().. - " "..cur_section.." Memory: "..( self.cache_max_memsize / 1024 ).." "..( self.cache_item_max_pixels / 1024 ).." "..( self.doc:getCacheSize() / 1024 ).." k", true) + " "..cur_section.. + " Memory: ".. + math.ceil( self.cache_current_memsize / 1024 ).."/"..( self.cache_max_memsize / 1024 ).. + " "..( self.cache_item_max_pixels / 1024 ).." "..( self.doc:getCacheSize() / 1024 ).." k", + true) ypos = ypos + 15 blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, From 3e83dcc2a4483b3c75d5d3bd3df01d804ee8348d Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 14:09:50 +0200 Subject: [PATCH 060/183] pass cache_document_size to document open This is how mupdf API is structured --- pdf.c | 2 +- pdfreader.lua | 2 +- unireader.lua | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/pdf.c b/pdf.c index 5583e09a0..df92901d3 100644 --- a/pdf.c +++ b/pdf.c @@ -166,7 +166,7 @@ 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 char buf[15]; - printf("cachesize: %s\n",readable_fs(cachesize,buf)); + printf("## cachesize: %s\n",readable_fs(cachesize,buf)); PdfDocument *doc = (PdfDocument*) lua_newuserdata(L, sizeof(PdfDocument)); diff --git a/pdfreader.lua b/pdfreader.lua index ec4e1a105..9413e0127 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -8,7 +8,7 @@ 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 diff --git a/unireader.lua b/unireader.lua index d4b163c84..148c2acc5 100644 --- a/unireader.lua +++ b/unireader.lua @@ -107,7 +107,7 @@ end -- open a file and its settings store -- tips: you can use self:loadSettings in open() method. -function UniReader:open(filename, password) +function UniReader:open(filename, cache_size) return false end @@ -149,7 +149,7 @@ end function UniReader:loadSettings(filename) if self.doc ~= nil then - self.settings = DocSettings:open(filename) + self.settings = DocSettings:open(filename,self.cache_document_size) local gamma = self.settings:readSetting("gamma") if gamma then @@ -169,8 +169,6 @@ function UniReader:loadSettings(filename) self.globalzoom = self.settings:readSetting("globalzoom") or 1.0 self.globalzoommode = self.settings:readSetting("globalzoommode") or -1 - self.doc:setCacheSize( self.cache_document_size ) - return true end return false From 4633ca08328f9dc21661e86bebfb067a45982099 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 31 Mar 2012 20:22:22 +0800 Subject: [PATCH 061/183] mod: use percent as absolute location in DOCs in CREReader The height of documents will be changed after zoom in or zoom out, so we cannot use pos to mark positions inside documents. --- cre.cpp | 21 ++++++++++++++++++++- crereader.lua | 26 ++++++++++++++++---------- 2 files changed, 36 insertions(+), 11 deletions(-) diff --git a/cre.cpp b/cre.cpp index 96c2aad0a..efd989863 100644 --- a/cre.cpp +++ b/cre.cpp @@ -84,6 +84,14 @@ static int getPos(lua_State *L) { return 1; } +static int getPosPercent(lua_State *L) { + CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); + + lua_pushinteger(L, doc->text_view->getPosPercent()); + + return 1; +} + static int getFullHeight(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); @@ -161,6 +169,15 @@ static int gotoPage(lua_State *L) { 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); @@ -223,9 +240,11 @@ static const struct luaL_Reg credocument_meth[] = { {"getPages", getNumberOfPages}, {"getCurrentPage", getCurrentPage}, {"getPos", getPos}, - {"GetFullHeight", getFullHeight}, + {"getPosPercent", getPosPercent}, + {"getFullHeight", getFullHeight}, {"getToc", getTableOfContent}, {"gotoPage", gotoPage}, + {"gotoPercent", gotoPercent}, {"gotoPos", gotoPos}, {"zoomFont", zoomFont}, {"drawCurrentPage", drawCurrentPage}, diff --git a/crereader.lua b/crereader.lua index 679df6d4a..c730eb846 100644 --- a/crereader.lua +++ b/crereader.lua @@ -3,6 +3,7 @@ require "inputbox" CREReader = UniReader:new{ pos = 0, + percent = 0, pan_overlap_vertical = 0, } @@ -26,11 +27,16 @@ function CREReader:open(filename) end function CREReader:getLastPageOrPos() - return self.settings:readSetting("last_pos") or 0 + local last_percent = self.settings:readSetting("last_percent") + if last_percent then + return (last_percent * self.doc:getFullHeight()) / 10000 + else + return 0 + end end function CREReader:saveLastPageOrPos() - self.settings:savesetting("last_pos", self.pos) + self.settings:savesetting("last_percent", self.percent) end function CREReader:setzoom(page, preCache) @@ -41,12 +47,12 @@ function CREReader:addJump(pos, notes) end function CREReader:goto(pos) - local pos = math.min(pos, self.doc:GetFullHeight()) + local pos = math.min(pos, self.doc:getFullHeight()) pos = math.max(pos, 0) -- add to jump_stack, distinguish jump from normal page turn if self.pos and math.abs(self.pos - pos) > height then - self:addJump(self.pos) + self:addJump(self.percent) end self.doc:gotoPos(pos) @@ -64,6 +70,7 @@ function CREReader:goto(pos) self.pos = pos self.pageno = self.doc:getCurrentPage() + self.percent = self.doc:getPosPercent() end function CREReader:redrawCurrentPage() @@ -73,7 +80,7 @@ end -- used in CREReader:showMenu() function CREReader:_drawReadingInfo() local ypos = height - 50 - local load_percent = (self.pos / self.doc:GetFullHeight()) + local load_percent = self.percent/100 fb.bb:paintRect(0, ypos, width, 50, 0) @@ -84,8 +91,7 @@ function CREReader:_drawReadingInfo() cur_section = "Section: "..cur_section end renderUtf8Text(fb.bb, 10, ypos+6, face, fhash, - "Position: "..math.floor((load_percent*100)).."%".. - " "..cur_section, true) + "Position: "..load_percent.."%".." "..cur_section, true) ypos = ypos + 15 blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, @@ -144,9 +150,9 @@ function CREReader:adjustCreReaderCommands() "jump to *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)) + 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 ) end From 2754d996c4da3a4f69f56497fd41fcfb50e18fae Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 14:32:36 +0200 Subject: [PATCH 062/183] move memory info to top of page This allows section name to be displayed on bottom, and it won't push memory info out of screen --- unireader.lua | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/unireader.lua b/unireader.lua index 148c2acc5..2ce78e177 100644 --- a/unireader.lua +++ b/unireader.lua @@ -817,15 +817,20 @@ function UniReader:showMenu() end renderUtf8Text(fb.bb, 10, ypos+6, face, fhash, "Page: "..self.pageno.."/"..self.doc:getPages().. - " "..cur_section.. - " Memory: ".. - math.ceil( self.cache_current_memsize / 1024 ).."/"..( self.cache_max_memsize / 1024 ).. - " "..( self.cache_item_max_pixels / 1024 ).." "..( self.doc:getCacheSize() / 1024 ).." k", - true) + " "..cur_section, true) ypos = ypos + 15 blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, 5, 4, load_percent, 8) + + -- display memory on top of page + fb.bb:paintRect(0, 0, width, 15+6*2, 0) + renderUtf8Text(fb.bb, 10, 15+6, face, fhash, + "Memory: ".. + math.ceil( self.cache_current_memsize / 1024 ).."/"..( self.cache_max_memsize / 1024 ).. + " "..( self.cache_item_max_pixels / 1024 ).." "..( self.cache_document_size / 1024 ).." k", + true) + fb:refresh(1) while 1 do local ev = input.waitForEvent() From 42c9773711a4a5af4e8a5115bfe9d5450c62c51e Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 31 Mar 2012 20:40:46 +0800 Subject: [PATCH 063/183] add: cache size control for djvureader --- djvu.c | 2 ++ djvureader.lua | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/djvu.c b/djvu.c index 6bcac2290..a5ab8380f 100644 --- a/djvu.c +++ b/djvu.c @@ -72,6 +72,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"); @@ -81,6 +82,7 @@ static int openDocument(lua_State *L) { if (! doc->context) { return luaL_error(L, "cannot create context."); } + 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)) diff --git a/djvureader.lua b/djvureader.lua index cd0ad9885..0d38faf55 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -6,7 +6,7 @@ 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, 10*1024*1024) if not ok then return ok, self.doc -- this will be the error message instead end From 5b1deedd869e18701f6504b421564020d61ae892 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 14:36:10 +0200 Subject: [PATCH 064/183] remove setCacheSize set cache in openDocument --- djvu.c | 14 ++++---------- djvureader.lua | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/djvu.c b/djvu.c index d60107920..b8d170bef 100644 --- a/djvu.c +++ b/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_checkint(L, 2); 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, 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); @@ -455,14 +458,6 @@ static int getCacheSize(lua_State *L) { return 1; } -static int setCacheSize(lua_State *L) { - DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); - int size = luaL_checkint(L, 2); - printf("## ddjvu_cache_set_size = %d\n", size); - ddjvu_cache_set_size(doc->context, size); - return 0; -} - static int cleanCache(lua_State *L) { DjvuDocument *doc = (DjvuDocument*) luaL_checkudata(L, 1, "djvudocument"); printf("## ddjvu_cache_clear\n"); @@ -482,7 +477,6 @@ static const struct luaL_Reg djvudocument_meth[] = { {"getPageText", getPageText}, {"close", closeDocument}, {"getCacheSize", getCacheSize}, - {"setCacheSize", setCacheSize}, {"cleanCache", cleanCache}, {"__gc", closeDocument}, {NULL, NULL} diff --git a/djvureader.lua b/djvureader.lua index cd0ad9885..be72ec4c0 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -6,7 +6,7 @@ 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 From b8941eee415922bf9226d3e3843cafce39525d71 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 14:44:09 +0200 Subject: [PATCH 065/183] cleanup cache_size based on coding guidelines --- pdf.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pdf.c b/pdf.c index df92901d3..ffa94be2e 100644 --- a/pdf.c +++ b/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); From a24fca5b5c8c4f706c90010f78b19ad575d3ba7b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 31 Mar 2012 21:09:06 +0800 Subject: [PATCH 066/183] mod: simulate full screen update in SDL emulator --- einkfb.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/einkfb.c b/einkfb.c index a1f7054f9..cfc268719 100644 --- a/einkfb.c +++ b/einkfb.c @@ -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."); } From 5884f69b60d43a28c33d573b5f5848df3e5fec59 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 15:10:20 +0200 Subject: [PATCH 067/183] setCacheSize cleanup --- unireader.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/unireader.lua b/unireader.lua index 2ce78e177..939e6848a 100644 --- a/unireader.lua +++ b/unireader.lua @@ -136,10 +136,6 @@ function UniReader:getCacheSize() return -1 end -function UniReader:setCacheSize(size) - return -end - function UniReader:cleanCache() return end From a035998b202d987ddfe3cb42329fd0ec38aefea6 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 31 Mar 2012 21:12:31 +0800 Subject: [PATCH 068/183] add: partial refresh configure entry in .readker.kpdfview.lua fix #78 --- unireader.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unireader.lua b/unireader.lua index b2cb982a1..9cb81fbc0 100644 --- a/unireader.lua +++ b/unireader.lua @@ -173,6 +173,11 @@ function UniReader:initGlobalSettings(settings) if cache_max_ttl then self.cache_max_ttl = cache_max_ttl end + + local rcountmax = settings:readSetting("partial_refresh_count") + if rcountmax then + self.rcountmax = rcountmax + end end -- guarantee that we have enough memory in cache @@ -509,7 +514,7 @@ function UniReader:show(no) self:toggleTextHighLight(self.highlight[no]) end - if self.rcount == self.rcountmax then + if self.rcount >= self.rcountmax then print("full refresh") self.rcount = 1 fb:refresh(0) From dc30c491f7c9121b582b230da1d898fdf95eece0 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 31 Mar 2012 21:17:29 +0800 Subject: [PATCH 069/183] add: Shift+R for manual full screen refresh fix #78 --- unireader.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/unireader.lua b/unireader.lua index 9cb81fbc0..96f0cbd64 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1049,6 +1049,12 @@ function UniReader:addAllCommands() unireader:showHighLight() unireader:goto(unireader.pageno) end) + self.commands:add(KEY_R, MOD_SHIFT, "R", + "manual full screen refresh", + function(unireader) + unireader.rcount = 1 + fb:refresh(0) + end) self.commands:add(KEY_HOME,nil,"Home", "exit application", function(unireader) From 661c7be893b7c025921d52255658d077f47a5609 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 15:29:17 +0200 Subject: [PATCH 070/183] use @traycold code from #74 to show current mupdf cache usage --- pdf.c | 13 +++++++++++++ unireader.lua | 5 +++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/pdf.c b/pdf.c index ffa94be2e..aa98c8626 100644 --- a/pdf.c +++ b/pdf.c @@ -424,6 +424,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} @@ -436,6 +447,8 @@ static const struct luaL_Reg pdfdocument_meth[] = { {"getPages", getNumberOfPages}, {"getTOC", getTableOfContent}, {"close", closeDocument}, + {"getCacheSize", getCacheSize}, + {"cleanCache", cleanCache}, {"__gc", closeDocument}, {NULL, NULL} }; diff --git a/unireader.lua b/unireader.lua index 939e6848a..e6c9a3740 100644 --- a/unireader.lua +++ b/unireader.lua @@ -823,8 +823,9 @@ function UniReader:showMenu() fb.bb:paintRect(0, 0, width, 15+6*2, 0) renderUtf8Text(fb.bb, 10, 15+6, face, fhash, "Memory: ".. - math.ceil( self.cache_current_memsize / 1024 ).."/"..( self.cache_max_memsize / 1024 ).. - " "..( self.cache_item_max_pixels / 1024 ).." "..( self.cache_document_size / 1024 ).." k", + math.ceil( self.cache_current_memsize / 1024 ).."/"..math.ceil( self.cache_max_memsize / 1024 ).. + " "..math.ceil( self.cache_item_max_pixels / 1024 ).." ".. + " "..math.ceil( self.doc:getCacheSize() / 1024 ).."/"..math.ceil( self.cache_document_size / 1024 ).." k", true) fb:refresh(1) From 244a6077ab996e45f21f0a541654e86b9293e24a Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 16:29:46 +0200 Subject: [PATCH 071/183] cache_item_max_pixels is not used #79 --- unireader.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/unireader.lua b/unireader.lua index db5eed43c..dd3ea6227 100644 --- a/unireader.lua +++ b/unireader.lua @@ -67,7 +67,6 @@ UniReader = { -- tile cache configuration: cache_max_memsize = 1024*1024*5, -- 5MB tile cache - cache_item_max_pixels = 1024*1024*2, -- max. size of rendered tiles cache_max_ttl = 20, -- time to live -- tile cache state: cache_current_memsize = 0, @@ -829,7 +828,6 @@ function UniReader:showMenu() renderUtf8Text(fb.bb, 10, 15+6, face, fhash, "Memory: ".. math.ceil( self.cache_current_memsize / 1024 ).."/"..math.ceil( self.cache_max_memsize / 1024 ).. - " "..math.ceil( self.cache_item_max_pixels / 1024 ).." ".. " "..math.ceil( self.doc:getCacheSize() / 1024 ).."/"..math.ceil( self.cache_document_size / 1024 ).." k", true) From 8764b8aa4fd5c6d624a39fdeb818420d71dffe09 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 1 Apr 2012 00:57:22 +0800 Subject: [PATCH 072/183] fix: force freeing biltbuffer on cache evict Also move cache display code above reading progress code. --- unireader.lua | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/unireader.lua b/unireader.lua index db5eed43c..bfee46a41 100644 --- a/unireader.lua +++ b/unireader.lua @@ -210,6 +210,7 @@ function UniReader:cacheClaim(size) else -- cache slot is at end of life, so kick it out self.cache_current_memsize = self.cache_current_memsize - self.cache[k].size + self.cache[k].bb:free() self.cache[k] = nil end end @@ -805,13 +806,22 @@ function UniReader:showHighLight() end function UniReader:showMenu() - local ypos = height - 50 local load_percent = (self.pageno / self.doc:getPages()) + local face, fhash = Font:getFaceAndHash(22) - fb.bb:paintRect(0, ypos, width, 50, 0) + -- display memory on top of page + fb.bb:paintRect(0, 0, width, 15+6*2, 0) + renderUtf8Text(fb.bb, 10, 15+6, face, fhash, + "Memory: ".. + math.ceil( self.cache_current_memsize / 1024 ).."/"..math.ceil( self.cache_max_memsize / 1024 ).. + " "..math.ceil( self.cache_item_max_pixels / 1024 ).." ".. + " "..math.ceil( self.doc:getCacheSize() / 1024 ).."/"..math.ceil( self.cache_document_size / 1024 ).." k", + true) + -- display reading progress on bottom of page + local ypos = height - 50 + fb.bb:paintRect(0, ypos, width, 50, 0) ypos = ypos + 15 - local face, fhash = Font:getFaceAndHash(22) local cur_section = self:getTOCTitleByPage(self.pageno) if cur_section ~= "" then cur_section = "Section: "..cur_section @@ -824,15 +834,6 @@ function UniReader:showMenu() blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, 5, 4, load_percent, 8) - -- display memory on top of page - fb.bb:paintRect(0, 0, width, 15+6*2, 0) - renderUtf8Text(fb.bb, 10, 15+6, face, fhash, - "Memory: ".. - math.ceil( self.cache_current_memsize / 1024 ).."/"..math.ceil( self.cache_max_memsize / 1024 ).. - " "..math.ceil( self.cache_item_max_pixels / 1024 ).." ".. - " "..math.ceil( self.doc:getCacheSize() / 1024 ).."/"..math.ceil( self.cache_document_size / 1024 ).." k", - true) - fb:refresh(1) while 1 do local ev = input.waitForEvent() From 989318661d8b89a77d3ff3ac4c44317fe33449f9 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 31 Mar 2012 19:47:39 +0200 Subject: [PATCH 073/183] disable djvu tools when cross-compiling --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8d9fb99b0..710ead11c 100644 --- a/Makefile +++ b/Makefile @@ -135,7 +135,7 @@ $(DJVULIBS): 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 From 2814102d28c0acb80529f2cd5a55662ffa38523b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 2 Apr 2012 11:52:34 +0800 Subject: [PATCH 074/183] mod: use customed cmake rules to build CREngine libraries --- .gitmodules | 4 ++-- Makefile | 26 ++++++++------------------ crengine | 1 - kpvcrlib/CMakeLists.txt | 39 +++++++++++++++++++++++++++++++++++++++ kpvcrlib/crengine | 1 + 5 files changed, 50 insertions(+), 21 deletions(-) delete mode 160000 crengine create mode 100644 kpvcrlib/CMakeLists.txt create mode 160000 kpvcrlib/crengine diff --git a/.gitmodules b/.gitmodules index 6c5c235cf..ead63e79f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,6 @@ [submodule "djvulibre"] path = djvulibre url = git://djvu.git.sourceforge.net/gitroot/djvu/djvulibre.git -[submodule "crengine"] - path = crengine +[submodule "kpvcrlib/crengine"] + path = kpvcrlib/crengine url = git://crengine.git.sourceforge.net/gitroot/crengine/crengine diff --git a/Makefile b/Makefile index e75b7ea76..05bd9bc60 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ MUPDFDIR=mupdf MUPDFTARGET=build/debug MUPDFLIBDIR=$(MUPDFDIR)/$(MUPDFTARGET) DJVUDIR=djvulibre -CRENGINEDIR=crengine +KPVCRLIGDIR=kpvcrlib +CRENGINEDIR=$(KPVCRLIGDIR)/crengine FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.8 LFSDIR=luafilesystem @@ -59,19 +60,13 @@ DJVULIBS := $(DJVUDIR)/build/libdjvu/.libs/libdjvulibre.a CRENGINELIBS := $(CRENGINEDIR)/crengine/libcrengine.a \ $(CRENGINEDIR)/thirdparty/chmlib/libchmlib.a \ $(CRENGINEDIR)/thirdparty/libpng/libpng.a \ - $(CRENGINEDIR)/thirdparty/libjpeg/libjpeg.a \ - $(CRENGINEDIR)/thirdparty/zlib/libz.a \ $(CRENGINEDIR)/thirdparty/antiword/libantiword.a THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \ $(MUPDFLIBDIR)/libopenjpeg.a \ $(MUPDFLIBDIR)/libjbig2dec.a \ + $(MUPDFLIBDIR)/libjpeg.a \ $(MUPDFLIBDIR)/libz.a -# @TODO the libjpeg used by mupdf is too new for crengine and will cause -# a segment fault when decoding jpeg images in crengine, we need to fix -# this. 28.03 2012 (houqp) - #$(MUPDFLIBDIR)/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) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) @@ -117,8 +112,6 @@ fetchthirdparty: -rm -Rf mupdf/thirdparty git submodule init git submodule update - 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 $(CRENGINEDIR)/thirdparty/zlib/qconfig.h || touch $(CRENGINEDIR)/thirdparty/zlib/qconfig.h test -f mupdf-thirdparty.zip || wget http://www.mupdf.com/download/mupdf-thirdparty.zip unzip mupdf-thirdparty.zip -d mupdf test -f lua-5.1.4.tar.gz || wget http://www.lua.org/ftp/lua-5.1.4.tar.gz @@ -130,7 +123,10 @@ clean: cleanthirdparty: make -C $(LUADIR) clean make -C $(MUPDFDIR) clean - make -C $(CRENGINEDIR) clean + make -C $(CRENGINEDIR)/thirdparty/antiword clean + make -C $(CRENGINEDIR)/thirdparty/chmlib clean + make -C $(CRENGINEDIR)/thirdparty/libpng clean + make -C $(CRENGINEDIR)/crengine clean -rm -rf $(DJVUDIR)/build -rm -f $(MUPDFDIR)/fontdump.host -rm -f $(MUPDFDIR)/cmapdump.host @@ -159,13 +155,7 @@ endif make -C $(DJVUDIR)/build $(CRENGINELIBS): - cd $(CRENGINEDIR) && cmake -D CR3_PNG=1 -D CR3_JPEG=1 . - cd $(CRENGINEDIR)/thirdparty/libjpeg && make - cd $(CRENGINEDIR)/thirdparty/chmlib && make - cd $(CRENGINEDIR)/thirdparty/antiword && make - cd $(CRENGINEDIR)/thirdparty/libpng && make - cd $(CRENGINEDIR)/thirdparty/zlib && make - cd $(CRENGINEDIR)/crengine && make + cd $(KPVCRLIGDIR) && CC="$(CC)" CXX="$(CXX)" cmake . && make $(LUALIB): make -C lua/src CC="$(CC)" CFLAGS="$(CFLAGS)" MYCFLAGS=-DLUA_USE_LINUX MYLIBS="-Wl,-E" liblua.a diff --git a/crengine b/crengine deleted file mode 160000 index e0a86d85a..000000000 --- a/crengine +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0a86d85a3da3bed48a84fd3096ee6040ba1ba34 diff --git a/kpvcrlib/CMakeLists.txt b/kpvcrlib/CMakeLists.txt new file mode 100644 index 000000000..59ac5c365 --- /dev/null +++ b/kpvcrlib/CMakeLists.txt @@ -0,0 +1,39 @@ +PROJECT(kpvcrlib) +cmake_minimum_required(VERSION 2.6) + +SET(CR_3RDPARTY_DIR crengine/thirdparty) + +SET(CR3_PNG 1) +SET(CR3_JPEG 1) + +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) + +INCLUDE_DIRECTORIES( + ${FREETYPE_INCLUDE_DIRS} + ${ANTIWORD_INCLUDE_DIR} + ${CHM_INCLUDE_DIRS} + ${PNG_INCLUDE_DIR} +) + +ADD_DEFINITIONS(-DUSE_FONTCONFIG=0 -DUSE_FREETYPE=1 -DCR3_PATCH=1 -DNDEBUG=1) + +#ADD_SUBDIRECTORY(crengine) + +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 ANTIWORD library") +ADD_DEFINITIONS(-DENABLE_ANTIWORD=1) +ADD_DEFINITIONS(-DCR3_ANTIWORD_PATCH=1) +ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/antiword) + +SET(GUI kpv) +ADD_SUBDIRECTORY(crengine/crengine) + diff --git a/kpvcrlib/crengine b/kpvcrlib/crengine new file mode 160000 index 000000000..ba469d334 --- /dev/null +++ b/kpvcrlib/crengine @@ -0,0 +1 @@ +Subproject commit ba469d33473670ca303e2ef7f9762452a86e18b1 From c48ed22fce13ca7df159e16b7593706cef9c377d Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 3 Apr 2012 23:55:19 +0800 Subject: [PATCH 075/183] fix: typo in drawOrCache() method --- unireader.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unireader.lua b/unireader.lua index f2ecd3626..d1924c481 100644 --- a/unireader.lua +++ b/unireader.lua @@ -282,7 +282,7 @@ function UniReader:drawOrCache(no, preCache) tile.y = 0 tile.w = self.fullwidth tile.h = self.fullheight - elseif (tile.w*tile.h / 2) > max_cache then + elseif (tile.w*tile.h / 2) < max_cache then -- no, we can't. so generate a tile as big as we can go -- grow area in steps of 10px while ((tile.w+10) * (tile.h+10) / 2) < max_cache do @@ -297,7 +297,7 @@ function UniReader:drawOrCache(no, preCache) tile.y = tile.y - 5 tile.h = tile.h + 5 end - if tile.y + tile.h < self.fullheigth then + if tile.y + tile.h < self.fullheight then tile.h = tile.h + 5 end end From 74ffdf019ec2b2b98e5e2c952a8701893756f6f3 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 4 Apr 2012 02:57:02 +0800 Subject: [PATCH 076/183] add: dirty hack for libjpeg as workaround no segfault now, but no images displayed. --- Makefile | 12 +++++++++++- cre.cpp | 2 +- kpvcrlib/CMakeLists.txt | 12 ++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 05bd9bc60..44dec6efc 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,10 @@ THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.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) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) @@ -112,8 +116,12 @@ fetchthirdparty: -rm -Rf mupdf/thirdparty git submodule init git submodule update + ln -s kpvcrlib/crengine/cr3gui/data data test -f mupdf-thirdparty.zip || wget http://www.mupdf.com/download/mupdf-thirdparty.zip unzip mupdf-thirdparty.zip -d mupdf + cd mupdf/thirdparty/jpeg-*/ && \ + patch -N -p0 < ../../../kpvcrlib/jpeg_compress_struct_size.patch &&\ + patch -N -p0 < ../../../kpvcrlib/jpeg_decompress_struct_size.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 @@ -155,7 +163,9 @@ endif make -C $(DJVUDIR)/build $(CRENGINELIBS): - cd $(KPVCRLIGDIR) && CC="$(CC)" CXX="$(CXX)" cmake . && make + 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 diff --git a/cre.cpp b/cre.cpp index efd989863..29337a2db 100644 --- a/cre.cpp +++ b/cre.cpp @@ -45,6 +45,7 @@ static int openDocument(lua_State *L) { doc->text_view = new LVDocView(); doc->text_view->setStyleSheet(lString8(style_sheet)); + doc->text_view->setBackgroundColor(0x000000); doc->text_view->LoadDocument(file_name); doc->text_view->setViewMode(DVM_SCROLL, -1); doc->text_view->Resize(width, height); @@ -209,7 +210,6 @@ static int drawCurrentPage(lua_State *L) { doc->text_view->Resize(w, h); doc->text_view->Render(); - drawBuf.Clear(0xFFFFFF); doc->text_view->Draw(drawBuf); diff --git a/kpvcrlib/CMakeLists.txt b/kpvcrlib/CMakeLists.txt index 59ac5c365..8e325b2e0 100644 --- a/kpvcrlib/CMakeLists.txt +++ b/kpvcrlib/CMakeLists.txt @@ -2,6 +2,7 @@ PROJECT(kpvcrlib) cmake_minimum_required(VERSION 2.6) SET(CR_3RDPARTY_DIR crengine/thirdparty) +SET(MUPDF_3RDPARTY_DIR ../mupdf/thirdparty) SET(CR3_PNG 1) SET(CR3_JPEG 1) @@ -10,18 +11,21 @@ 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(JPEGLIB_INCLUDE_DIR ${MUPDF_3RDPARTY_DIR}/jpeg-8d) +#SET(JPEGLIB_INCLUDE_DIR ${CR_3RDPARTY_DIR}/libjpeg) INCLUDE_DIRECTORIES( ${FREETYPE_INCLUDE_DIRS} ${ANTIWORD_INCLUDE_DIR} ${CHM_INCLUDE_DIRS} ${PNG_INCLUDE_DIR} + ${ZLIB_INCLUDE_DIR} + ${JPEGLIB_INCLUDE_DIR} ) ADD_DEFINITIONS(-DUSE_FONTCONFIG=0 -DUSE_FREETYPE=1 -DCR3_PATCH=1 -DNDEBUG=1) -#ADD_SUBDIRECTORY(crengine) - message("Will build patched LIBCHM library") ADD_DEFINITIONS(-DCHM_SUPPORT_ENABLED=1) ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/chmlib) @@ -29,11 +33,15 @@ 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 build patched ANTIWORD library") ADD_DEFINITIONS(-DENABLE_ANTIWORD=1) ADD_DEFINITIONS(-DCR3_ANTIWORD_PATCH=1) ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/antiword) SET(GUI kpv) +ADD_DEFINITIONS(-DJCONFIG_INCLUDE=1) ADD_SUBDIRECTORY(crengine/crengine) From bb5e28883c807db32d67c2221974e5ea3a885260 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 4 Apr 2012 03:00:20 +0800 Subject: [PATCH 077/183] add: patch files for previous commit --- .gitignore | 4 ++++ kpvcrlib/jpeg_compress_struct_size.patch | 15 +++++++++++++++ kpvcrlib/jpeg_decompress_struct_size.patch | 15 +++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 kpvcrlib/jpeg_compress_struct_size.patch create mode 100644 kpvcrlib/jpeg_decompress_struct_size.patch diff --git a/.gitignore b/.gitignore index 21f649e41..81a4bd850 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,7 @@ kindlepdfviewer-*.zip /.project /.reader.kpdfview +kpvcrlib/CMakeCache.txt +kpvcrlib/CMakeFiles/ +kpvcrlib/cmake_install.cmake +kpvcrlib/Makefile diff --git a/kpvcrlib/jpeg_compress_struct_size.patch b/kpvcrlib/jpeg_compress_struct_size.patch new file mode 100644 index 000000000..e2da6de22 --- /dev/null +++ b/kpvcrlib/jpeg_compress_struct_size.patch @@ -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 diff --git a/kpvcrlib/jpeg_decompress_struct_size.patch b/kpvcrlib/jpeg_decompress_struct_size.patch new file mode 100644 index 000000000..deaf375a1 --- /dev/null +++ b/kpvcrlib/jpeg_decompress_struct_size.patch @@ -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 From 8b6398b58e38a873b9e95c1ab34e0f38d3f6f222 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 4 Apr 2012 15:27:01 +0800 Subject: [PATCH 078/183] mod: small changes for kindle build --- Makefile | 5 ++++- kpvcrlib/CMakeLists.txt | 10 +++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 44dec6efc..557293ea8 100644 --- a/Makefile +++ b/Makefile @@ -135,6 +135,7 @@ cleanthirdparty: 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 @@ -164,7 +165,7 @@ endif $(CRENGINELIBS): cd $(KPVCRLIGDIR) && rm -rf CMakeCache.txt CMakeFiles && \ - CFLAGS="$(CFLAGS)" CC="$(CC)" CXX="$(CXX)" cmake . && \ + CC="$(CC)" CXX="$(CXX)" cmake . && \ make $(LUALIB): @@ -185,6 +186,8 @@ customupdate: kpdfview file kpdfview | grep ARM || exit 1 mkdir $(INSTALL_DIR) cp -p README.TXT COPYING kpdfview *.lua $(INSTALL_DIR) + cp -rpL data $(INSTALL_DIR) + cp -rp fonts $(INSTALL_DIR) zip -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" diff --git a/kpvcrlib/CMakeLists.txt b/kpvcrlib/CMakeLists.txt index 8e325b2e0..19bafa56b 100644 --- a/kpvcrlib/CMakeLists.txt +++ b/kpvcrlib/CMakeLists.txt @@ -1,19 +1,22 @@ 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(MUPDF_3RDPARTY_DIR ../mupdf/thirdparty) SET(CR3_PNG 1) -SET(CR3_JPEG 1) +#SET(CR3_JPEG 1) 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} @@ -22,6 +25,7 @@ INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} ${JPEGLIB_INCLUDE_DIR} + ${JCONFIG_INCLUDE_DIR} ) ADD_DEFINITIONS(-DUSE_FONTCONFIG=0 -DUSE_FREETYPE=1 -DCR3_PATCH=1 -DNDEBUG=1) @@ -42,6 +46,6 @@ ADD_DEFINITIONS(-DCR3_ANTIWORD_PATCH=1) ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/antiword) SET(GUI kpv) -ADD_DEFINITIONS(-DJCONFIG_INCLUDE=1) +#ADD_DEFINITIONS(-DJCONFIG_INCLUDED=1) ADD_SUBDIRECTORY(crengine/crengine) From b9b9455fe0d99f3c5b7ecf42d10b01e2d9c83eae Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 4 Apr 2012 17:03:01 +0800 Subject: [PATCH 079/183] fix: progressbar display in crereader --- crereader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crereader.lua b/crereader.lua index c730eb846..7575e3d9b 100644 --- a/crereader.lua +++ b/crereader.lua @@ -95,7 +95,7 @@ function CREReader:_drawReadingInfo() ypos = ypos + 15 blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, - 5, 4, load_percent, 8) + 5, 4, load_percent/100, 8) end function CREReader:nextView() From f90ae38cb5dbf0e1374a41480c4b2afdc1799767 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 4 Apr 2012 17:25:18 +0800 Subject: [PATCH 080/183] mod: load css in crereader --- cre.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cre.cpp b/cre.cpp index 29337a2db..54df5b830 100644 --- a/cre.cpp +++ b/cre.cpp @@ -36,19 +36,26 @@ typedef struct 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->setStyleSheet(lString8(style_sheet)); doc->text_view->setBackgroundColor(0x000000); - doc->text_view->LoadDocument(file_name); + 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->text_view->Render(); return 1; From 17823a0b589aadb1f78aee850434764d8bce6ec7 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 4 Apr 2012 20:24:17 +0800 Subject: [PATCH 081/183] mod: add cflags in crereader compiling --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 557293ea8..709fab96c 100644 --- a/Makefile +++ b/Makefile @@ -165,7 +165,7 @@ endif $(CRENGINELIBS): cd $(KPVCRLIGDIR) && rm -rf CMakeCache.txt CMakeFiles && \ - CC="$(CC)" CXX="$(CXX)" cmake . && \ + CFLAGS="$(CFLAGS)" CC="$(CC)" CXX="$(CXX)" cmake . && \ make $(LUALIB): From 081a4165bb2ea2fc688fd47fc0fda58f85a9bb16 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 4 Apr 2012 20:28:09 +0800 Subject: [PATCH 082/183] mod: add kpdview crash log --- launchpad/kpdf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index 6d1eba1a4..35ff719ce 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -4,5 +4,5 @@ export LC_ALL="en_US.UTF-8" echo unlock > /proc/keypad echo unlock > /proc/fiveway cd /mnt/us/kindlepdfviewer/ -./reader.lua $1 +./reader.lua $1 2> /mnt/us/kindlepdfviewer/crash.log echo 1 > /proc/eink_fb/update_display From 1593c568033be0e2df2b80e7253debec552c6a54 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 5 Apr 2012 10:31:31 +0800 Subject: [PATCH 083/183] mod: add html support in crereader --- crereader.lua | 3 +++ filechooser.lua | 6 +++++- filesearcher.lua | 1 + reader.lua | 2 +- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crereader.lua b/crereader.lua index 7575e3d9b..79733ae91 100644 --- a/crereader.lua +++ b/crereader.lua @@ -16,6 +16,9 @@ end function CREReader:open(filename) local ok local file_type = string.lower(string.match(filename, ".+%.([^.]+)")) + 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, width, height) diff --git a/filechooser.lua b/filechooser.lua index 58c294f65..cb5a5aaf5 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -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" or file_type == "epub" or file_type == "txt" or file_type == "rtf" or file_type == "htm" or file_type == "fb2" or file_type == "chm" 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 diff --git a/filesearcher.lua b/filesearcher.lua index 097fb5c57..31ec6e371 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -38,6 +38,7 @@ function FileSearcher:readDir() 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) diff --git a/reader.lua b/reader.lua index adbd99447..f74f5d1f5 100755 --- a/reader.lua +++ b/reader.lua @@ -43,7 +43,7 @@ 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 == "fb2" or file_type == "chm" then + 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 From c41e3e75a3d09f5c1db6075c638df6e1e72b031b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 13:51:37 +0800 Subject: [PATCH 084/183] add: demo for fixing #85, #86 --- Makefile | 7 +++- input.c | 4 +-- keys.lua | 3 ++ launchpad/kpdf.sh | 9 +++++ reader.lua | 8 +++-- screen.lua | 4 ++- slider_watcher.c | 87 +++++++++++++++++++++++++++++++++++++++++++++++ unireader.lua | 16 +++++++++ 8 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 slider_watcher.c diff --git a/Makefile b/Makefile index 8d9fb99b0..35d75ffa5 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,8 @@ THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \ LUALIB := $(LUADIR)/src/liblua.a +all:kpdfview slider_watcher + 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++ \ kpdfview.o \ @@ -84,6 +86,9 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft 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 $< -o $@ @@ -107,7 +112,7 @@ fetchthirdparty: 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 diff --git a/input.c b/input.c index 05eedaa49..2f7853d2c 100644 --- a/input.c +++ b/input.c @@ -22,8 +22,8 @@ #include #include "input.h" -#define NUM_FDS 3 -int inputfds[3] = { -1, -1, -1 }; +#define NUM_FDS 4 +int inputfds[4] = { -1, -1, -1, -1 }; static int openInputDevice(lua_State *L) { #ifndef EMULATE_READER diff --git a/keys.lua b/keys.lua index 489a93e05..0c155282e 100644 --- a/keys.lua +++ b/keys.lua @@ -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 EV_KEY = 1 diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index 35ff719ce..0a107c5d4 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -1,8 +1,17 @@ #!/bin/sh +SLIDER_EVENT_PIPE="/tmp/event_slider" export LC_ALL="en_US.UTF-8" echo unlock > /proc/keypad echo unlock > /proc/fiveway cd /mnt/us/kindlepdfviewer/ + +# create the named pipe for power slider event +if [ ! -p $SLIDER_EVENT_PIPE ]; then + mkfifo $SLIDER_EVENT_PIPE +fi +killall slider_watcher +./slider_watcher $SLIDER_EVENT_PIPE & + ./reader.lua $1 2> /mnt/us/kindlepdfviewer/crash.log echo 1 > /proc/eink_fb/update_display diff --git a/reader.lua b/reader.lua index c197d55ee..b340aa05a 100755 --- a/reader.lua +++ b/reader.lua @@ -92,6 +92,7 @@ if optarg["d"] == "k3" then input.open("/dev/input/event0") input.open("/dev/input/event1") input.open("/dev/input/event2") + input.open("/tmp/event_slider") setK3Keycodes() elseif optarg["d"] == "emu" then input.open("") @@ -100,6 +101,7 @@ elseif optarg["d"] == "emu" then else input.open("/dev/input/event0") input.open("/dev/input/event1") + input.open("/tmp/event_slider") -- check if we are running on Kindle 3 (additional volume input) local f=lfs.attributes("/dev/input/event2") @@ -119,7 +121,7 @@ fb = einkfb.open("/dev/fb0") width, 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") @@ -167,10 +169,10 @@ 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 + os.execute("killall -cont cvm") os.execute('echo "send '..KEY_MENU..'" > /proc/keypad;echo "send '..KEY_MENU..'" > /proc/keypad') end diff --git a/screen.lua b/screen.lua index 24923a2e7..6d6022498 100644 --- a/screen.lua +++ b/screen.lua @@ -41,6 +41,9 @@ 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, } -- @orien: 1 for clockwise rotate, -1 for anti-clockwise @@ -71,4 +74,3 @@ function Screen:updateRotationMode() end end - diff --git a/slider_watcher.c b/slider_watcher.c new file mode 100644 index 000000000..6a4e26e15 --- /dev/null +++ b/slider_watcher.c @@ -0,0 +1,87 @@ +/* + KindlePDFViewer: power slider key event watcher + Copyright (C) 2012 Qingping Hou + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/unireader.lua b/unireader.lua index d1924c481..fb206b708 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1211,5 +1211,21 @@ function UniReader:addAllCommands() end end) -- end panning + self.commands:add(KEY_INTO_SCREEN_SAVER,nil,"slider", + "toggle screen saver", + function(unireader) + Screen.kpv_rotation_mode = Screen.cur_rotation_mode + fb:setOrientation(Screen.native_rotation_mode) + os.execute("killall -cont cvm") + print("resumed") + end) + self.commands:add(KEY_OUTOF_SCREEN_SAVER,nil,"slider", + "toggle screen saver", + function(unireader) + os.execute("sleep 1") + os.execute("killall -stop cvm") + fb:setOrientation(Screen.kpv_rotation_mode) + unireader:goto(unireader.pageno) + end) print("## defined commands "..dump(self.commands.map)) end From d3e8c57bc8401f69066d134e29e6b3096cf043fa Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 16:34:53 +0800 Subject: [PATCH 085/183] mod: reenable global pan_overlap_vertical in crereader --- crereader.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/crereader.lua b/crereader.lua index 79733ae91..e1004b299 100644 --- a/crereader.lua +++ b/crereader.lua @@ -4,7 +4,6 @@ require "inputbox" CREReader = UniReader:new{ pos = 0, percent = 0, - pan_overlap_vertical = 0, } function CREReader:init() From 085d79d033f7d8fcacaf2715c7a2dc18c1358065 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 18:05:54 +0800 Subject: [PATCH 086/183] add: font menu and bold attribute toggle shortcut in crereader --- cre.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++++++++-- crereader.lua | 28 ++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 3 deletions(-) diff --git a/cre.cpp b/cre.cpp index 54df5b830..7ec155d21 100644 --- a/cre.cpp +++ b/cre.cpp @@ -54,7 +54,6 @@ static int openDocument(lua_State *L) { } doc->text_view->setViewMode(DVM_SCROLL, -1); doc->text_view->Resize(width, height); - doc->text_view->LoadDocument(file_name); doc->text_view->Render(); @@ -153,7 +152,8 @@ static int walkTableOfContent(lua_State *L, LVTocItem *toc, int *count) { * } * * Warnning: not like pdf or djvu support, page here refers to the - * position(height) within the document, not the real page number. + * 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) { @@ -168,6 +168,41 @@ static int getTableOfContent(lua_State *L) { 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); @@ -206,6 +241,14 @@ static int zoomFont(lua_State *L) { return 1; } +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 drawCurrentPage(lua_State *L) { CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument"); DrawContext *dc = (DrawContext*) luaL_checkudata(L, 2, "drawcontext"); @@ -240,20 +283,26 @@ static int drawCurrentPage(lua_State *L) { static const struct luaL_Reg cre_func[] = { {"openDocument", openDocument}, + {"getFontFaces", getFontFaces}, {NULL, NULL} }; static const struct luaL_Reg credocument_meth[] = { + /* get methods */ {"getPages", getNumberOfPages}, {"getCurrentPage", getCurrentPage}, {"getPos", getPos}, {"getPosPercent", getPosPercent}, {"getFullHeight", getFullHeight}, {"getToc", getTableOfContent}, + /* set methods */ + {"setFontFace", setFontFace}, + /* control methods */ {"gotoPage", gotoPage}, {"gotoPercent", gotoPercent}, {"gotoPos", gotoPos}, {"zoomFont", zoomFont}, + {"toggleFontBolder", toggleFontBolder}, {"drawCurrentPage", drawCurrentPage}, {"close", closeDocument}, {"__gc", closeDocument}, diff --git a/crereader.lua b/crereader.lua index e1004b299..9ab7b5d81 100644 --- a/crereader.lua +++ b/crereader.lua @@ -1,5 +1,6 @@ require "unireader" require "inputbox" +require "selectmenu" CREReader = UniReader:new{ pos = 0, @@ -15,6 +16,7 @@ end 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 @@ -125,7 +127,6 @@ function CREReader:adjustCreReaderCommands() 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, nil, "F") self.commands:del(KEY_F, MOD_SHIFT, "F") self.commands:del(KEY_F, MOD_ALT, "F") @@ -157,4 +158,29 @@ function CREReader:adjustCreReaderCommands() 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, height) + print(face_list[item_no]) + if item_no then + cr.doc:setFontFace(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 + ) end From a5a5c477acc9189af5bd5876f818fb12f6857365 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 19:11:18 +0800 Subject: [PATCH 087/183] mod: save and restore font face for each book in crereader --- crereader.lua | 13 +++++++++++++ unireader.lua | 37 +++++++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/crereader.lua b/crereader.lua index 9ab7b5d81..55196c265 100644 --- a/crereader.lua +++ b/crereader.lua @@ -5,6 +5,8 @@ require "selectmenu" CREReader = UniReader:new{ pos = 0, percent = 0, + + font_face = nil, } function CREReader:init() @@ -30,6 +32,16 @@ function CREReader:open(filename) return true end +function CREReader:loadSpecialSettings() + local font_face = self.settings:readSetting("font_face") + self.font_face = font_face or "FreeSerif" + self.doc:setFontFace(self.font_face) +end + +function CREReader:saveSpecialSettings() + self.settings:savesetting("font_face", self.font_face) +end + function CREReader:getLastPageOrPos() local last_percent = self.settings:readSetting("last_percent") if last_percent then @@ -172,6 +184,7 @@ function CREReader:adjustCreReaderCommands() 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 diff --git a/unireader.lua b/unireader.lua index 706a43730..d37fc1f87 100644 --- a/unireader.lua +++ b/unireader.lua @@ -94,24 +94,23 @@ end -- !!!!!!!!!!!!!!!!!!!!!!!!! -- -- For a new specific reader, --- you must always overwrite following two methods: +-- you must always overwrite following method: -- -- * self:open() --- * self:init() -- -- overwrite other methods if needed. ---------------------------------------------------- -function UniReader:init() - -- initialize commands - self:addAllCommands() -end --- open a file and its settings store --- tips: you can use self:loadSettings in open() method. +-- open a file function UniReader:open(filename, cache_size) return false end +function UniReader:init() + -- initialize commands + self:addAllCommands() +end + ---------------------------------------------------- -- You need to overwrite following two methods if your -- reader supports highlight feature. @@ -130,7 +129,7 @@ function UniReader:toggleTextHighLight(word_list) end ---------------------------------------------------- --- renderer memory +-- Renderer memory ---------------------------------------------------- function UniReader:getCacheSize() @@ -141,8 +140,23 @@ function UniReader:cleanCache() return end +---------------------------------------------------- +-- Setting related methods +---------------------------------------------------- + +-- load special settings for specific reader +function UniReader:loadSpecialSettings() + return +end + +-- save special settings for specific reader +function UniReader:saveSpecialSettings() +end + + --[ following are default methods ]-- + function UniReader:initGlobalSettings(settings) local pan_overlap_vertical = settings:readSetting("pan_overlap_vertical") if pan_overlap_vertical then @@ -165,6 +179,7 @@ function UniReader:initGlobalSettings(settings) end end +-- This is a low-level method that can be shared with all readers. function UniReader:loadSettings(filename) if self.doc ~= nil then self.settings = DocSettings:open(filename,self.cache_document_size) @@ -187,6 +202,7 @@ function UniReader:loadSettings(filename) self.globalzoom = self.settings:readSetting("globalzoom") or 1.0 self.globalzoommode = self.settings:readSetting("globalzoommode") or -1 + self:loadSpecialSettings() return true end return false @@ -913,6 +929,7 @@ function UniReader:inputLoop() self.settings:savesetting("globalzoom", self.globalzoom) self.settings:savesetting("globalzoommode", self.globalzoommode) self.settings:savesetting("highlight", self.highlight) + self:saveSpecialSettings() self.settings:close() end @@ -1228,5 +1245,5 @@ function UniReader:addAllCommands() end end) -- end panning - print("## defined commands "..dump(self.commands.map)) + --print("## defined commands "..dump(self.commands.map)) end From 9650a4a0573a91d5d7ad85a39f590c3c8dbf4ff7 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 19:30:10 +0800 Subject: [PATCH 088/183] add: gamma settings in crereader --- cre.cpp | 18 ++++++++++++++++++ crereader.lua | 23 +++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/cre.cpp b/cre.cpp index 7ec155d21..bde307520 100644 --- a/cre.cpp +++ b/cre.cpp @@ -57,9 +57,25 @@ static int openDocument(lua_State *L) { doc->text_view->LoadDocument(file_name); doc->text_view->Render(); + printf("gamma: %d\n", fontMan->GetGammaIndex()); + + 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; @@ -284,6 +300,8 @@ static int drawCurrentPage(lua_State *L) { static const struct luaL_Reg cre_func[] = { {"openDocument", openDocument}, {"getFontFaces", getFontFaces}, + {"getGammaIndex", getGammaIndex}, + {"setGammaIndex", setGammaIndex}, {NULL, NULL} }; diff --git a/crereader.lua b/crereader.lua index 55196c265..ba1d77e86 100644 --- a/crereader.lua +++ b/crereader.lua @@ -6,6 +6,7 @@ CREReader = UniReader:new{ pos = 0, percent = 0, + gamma_index = 15, font_face = nil, } @@ -36,10 +37,15 @@ 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) end function CREReader:saveSpecialSettings() self.settings:savesetting("font_face", self.font_face) + self.settings:savesetting("gamma_index", self.gamma_index) end function CREReader:getLastPageOrPos() @@ -60,6 +66,7 @@ function CREReader:setzoom(page, preCache) end function CREReader:addJump(pos, notes) + return end function CREReader:goto(pos) @@ -196,4 +203,20 @@ function CREReader:adjustCreReaderCommands() cr:redrawCurrentPage() 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 + ) end From 5e58a94632d37eca70b891cff587ea412ca16a35 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 20:13:46 +0800 Subject: [PATCH 089/183] mod: rm unused shortcuts in crereader --- commands.lua | 11 +++++++++++ crereader.lua | 1 + djvureader.lua | 21 +++++++++++++++++++++ unireader.lua | 12 ------------ 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/commands.lua b/commands.lua index 2b5b58aef..1d7064b03 100644 --- a/commands.lua +++ b/commands.lua @@ -88,6 +88,7 @@ function Commands:addGroup(keygroup,keys,help,func) end end +--@TODO handle MOD_ANY 06.04 2012 (houqp) function Commands:del(keycode, modifier, keydescr) local keydef = nil @@ -106,6 +107,16 @@ function Commands:del(keycode, modifier, keydescr) 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) diff --git a/crereader.lua b/crereader.lua index ba1d77e86..41fc5e20c 100644 --- a/crereader.lua +++ b/crereader.lua @@ -131,6 +131,7 @@ end function CREReader:adjustCreReaderCommands() -- delete commands + self.commands:delGroup("[joypad]") self.commands:del(KEY_G, nil, "G") self.commands:del(KEY_J, nil, "J") self.commands:del(KEY_K, nil, "K") diff --git a/djvureader.lua b/djvureader.lua index be72ec4c0..c9ec0cdf4 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -13,6 +13,27 @@ function DJVUReader:open(filename) return ok end +function DJVUReader:init() + self:addAllCommands() + self:adjustDjvuReaderCommand() +end + +function DJVUReader:adjustDjvuReaderCommand() + self.commands:add(KEY_N, nil, "N", + "start highlight mode", + function(unireader) + unireader:startHighLightMode() + unireader:goto(unireader.pageno) + end + ) + self.commands:add(KEY_N, MOD_SHIFT, "N", + "display all highlights", + function(unireader) + unireader:showHighLight() + unireader:goto(unireader.pageno) + end + ) +end -----------[ highlight support ]---------- diff --git a/unireader.lua b/unireader.lua index d37fc1f87..4d010de80 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1096,18 +1096,6 @@ function UniReader:addAllCommands() function(unireader) unireader:screenRotate("anticlockwise") end) - self.commands:add(KEY_N, nil, "N", - "start highlight mode", - function(unireader) - unireader:startHighLightMode() - unireader:goto(unireader.pageno) - end) - self.commands:add(KEY_N, MOD_SHIFT, "N", - "display all highlights", - function(unireader) - unireader:showHighLight() - unireader:goto(unireader.pageno) - end) self.commands:add(KEY_R, MOD_SHIFT, "R", "manual full screen refresh", function(unireader) From 3992fdca0f9a2a23da89525556e4ab9cc4e50fd2 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 20:26:53 +0800 Subject: [PATCH 090/183] add: vertical pan in crereader --- crereader.lua | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crereader.lua b/crereader.lua index 41fc5e20c..9b5358765 100644 --- a/crereader.lua +++ b/crereader.lua @@ -43,11 +43,6 @@ function CREReader:loadSpecialSettings() cre.setGammaIndex(self.gamma_index) end -function CREReader:saveSpecialSettings() - self.settings:savesetting("font_face", self.font_face) - self.settings:savesetting("gamma_index", self.gamma_index) -end - function CREReader:getLastPageOrPos() local last_percent = self.settings:readSetting("last_percent") if last_percent then @@ -57,6 +52,11 @@ function CREReader:getLastPageOrPos() end end +function CREReader:saveSpecialSettings() + self.settings:savesetting("font_face", self.font_face) + self.settings:savesetting("gamma_index", self.gamma_index) +end + function CREReader:saveLastPageOrPos() self.settings:savesetting("last_percent", self.percent) end @@ -220,4 +220,16 @@ function CREReader:adjustCreReaderCommands() 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 From 43288b55ece964be54f981b8927471d116ffe6da Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 21:17:25 +0800 Subject: [PATCH 091/183] fix: handle zero used box --- unireader.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unireader.lua b/unireader.lua index fb206b708..5fdd91f85 100644 --- a/unireader.lua +++ b/unireader.lua @@ -347,6 +347,8 @@ function UniReader:setzoom(page, preCache) x1 = pwidth y1 = pheight end + if x1 == 0 then x1 = pwidth end + if y1 == 0 then y1 = pheight end -- clamp to page BBox if x0 < 0 then x0 = 0 end if x1 > pwidth then x1 = pwidth end From 99f3d405b0bf58911416f9321012492b873f842d Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 6 Apr 2012 21:21:21 +0800 Subject: [PATCH 092/183] add: slider_watcher to customupdate target --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 35d75ffa5..0d33ebf1e 100644 --- a/Makefile +++ b/Makefile @@ -161,7 +161,7 @@ customupdate: kpdfview # ensure that build binary is for ARM file kpdfview | grep ARM || exit 1 mkdir $(INSTALL_DIR) - cp -p README.TXT COPYING kpdfview *.lua $(INSTALL_DIR) + cp -p README.TXT COPYING kpdfview slider_watcher *.lua $(INSTALL_DIR) zip -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" From 9d11916bbd608edf6ac1f15f6d7b8dec1f9f9a7e Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 7 Apr 2012 10:05:27 +0800 Subject: [PATCH 093/183] mod: resume framework in kpdf.sh --- launchpad/kpdf.sh | 1 + reader.lua | 2 +- unireader.lua | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index 0a107c5d4..e43bc5c04 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -14,4 +14,5 @@ killall slider_watcher ./slider_watcher $SLIDER_EVENT_PIPE & ./reader.lua $1 2> /mnt/us/kindlepdfviewer/crash.log +killall -cont cvm echo 1 > /proc/eink_fb/update_display diff --git a/reader.lua b/reader.lua index b340aa05a..6bb1e11fd 100755 --- a/reader.lua +++ b/reader.lua @@ -173,6 +173,6 @@ fb:setOrientation(Screen.native_rotation_mode) input.closeAll() if optarg["d"] ~= "emu" then - os.execute("killall -cont cvm") + --os.execute("killall -cont cvm") os.execute('echo "send '..KEY_MENU..'" > /proc/keypad;echo "send '..KEY_MENU..'" > /proc/keypad') end diff --git a/unireader.lua b/unireader.lua index 5fdd91f85..d4f3b64f5 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1219,7 +1219,6 @@ function UniReader:addAllCommands() Screen.kpv_rotation_mode = Screen.cur_rotation_mode fb:setOrientation(Screen.native_rotation_mode) os.execute("killall -cont cvm") - print("resumed") end) self.commands:add(KEY_OUTOF_SCREEN_SAVER,nil,"slider", "toggle screen saver", From 3160ae15f4eaf2b3fcf9e1230092781d6c86bee4 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 7 Apr 2012 13:37:13 +0200 Subject: [PATCH 094/183] make fetchthirdparty fails on existing data dir #77 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 709fab96c..f9b2c1e32 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,7 @@ fetchthirdparty: -rm -Rf mupdf/thirdparty git submodule init git submodule update - ln -s kpvcrlib/crengine/cr3gui/data data + ln -sf kpvcrlib/crengine/cr3gui/data data test -f mupdf-thirdparty.zip || wget http://www.mupdf.com/download/mupdf-thirdparty.zip unzip mupdf-thirdparty.zip -d mupdf cd mupdf/thirdparty/jpeg-*/ && \ From f1ba76bfdc779f78ee7471746fee3f230052a411 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 7 Apr 2012 13:48:21 +0200 Subject: [PATCH 095/183] create fonts symlink to TTF_FONTS_DIR #77 It took me few tries to figure out that fonts should point to directory with *.ttf files as opposed to directory with subdirectories so I decided to add comment about it --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index f9b2c1e32..8ff43ed3a 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,9 @@ CRENGINEDIR=$(KPVCRLIGDIR)/crengine FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.8 LFSDIR=luafilesystem +# must point to directory with *.ttf fonts for crengine +TTF_FONTS_DIR=/usr/share/fonts/truetype/freefont/ + # set this to your ARM cross compiler: CC:=arm-unknown-linux-gnueabi-gcc @@ -117,6 +120,7 @@ fetchthirdparty: git submodule init git submodule update ln -sf kpvcrlib/crengine/cr3gui/data data + test -d fonts || ln -sf $(TTF_FONTS_DIR) fonts test -f mupdf-thirdparty.zip || wget http://www.mupdf.com/download/mupdf-thirdparty.zip unzip mupdf-thirdparty.zip -d mupdf cd mupdf/thirdparty/jpeg-*/ && \ From cb83e478758062e2f3f95aa31e52b943e97dada2 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 7 Apr 2012 22:55:56 +0800 Subject: [PATCH 096/183] mod: use XPointer as absolute postion indicator inside document --- cre.cpp | 122 +++++++++++++++++++++++++++++++++++++++++++++----- crereader.lua | 111 +++++++++++++++++++++++++++++++++++++-------- unireader.lua | 32 +++++++++---- 3 files changed, 226 insertions(+), 39 deletions(-) diff --git a/cre.cpp b/cre.cpp index bde307520..27260bd63 100644 --- a/cre.cpp +++ b/cre.cpp @@ -30,6 +30,7 @@ extern "C" { typedef struct CreDocument { LVDocView *text_view; + ldomDocument *dom_doc; } CreDocument; @@ -55,9 +56,9 @@ static int openDocument(lua_State *L) { 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(); - printf("gamma: %d\n", fontMan->GetGammaIndex()); return 1; } @@ -99,7 +100,20 @@ static int getCurrentPage(lua_State *L) { return 1; } -static int getPos(lua_State *L) { +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()); @@ -107,7 +121,22 @@ static int getPos(lua_State *L) { return 1; } -static int getPosPercent(lua_State *L) { +//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()); @@ -115,6 +144,15 @@ static int getPosPercent(lua_State *L) { 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"); @@ -138,7 +176,13 @@ static int walkTableOfContent(lua_State *L, LVTocItem *toc, int *count) { /* set subtable, Toc entry */ lua_newtable(L); lua_pushstring(L, "page"); - lua_pushnumber(L, toc_tmp->getY()); + 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"); @@ -163,8 +207,18 @@ static int walkTableOfContent(lua_State *L, LVTocItem *toc, int *count) { /* * Return a table like this: * { - * {page=12, depth=1, title="chapter1"}, - * {page=54, depth=1, title="chapter2"}, + * { + * 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 @@ -246,6 +300,21 @@ static int gotoPos(lua_State *L) { 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 correctionColor + * 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"); @@ -265,6 +334,32 @@ static int toggleFontBolder(lua_State *L) { 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"); @@ -306,21 +401,26 @@ static const struct luaL_Reg cre_func[] = { }; static const struct luaL_Reg credocument_meth[] = { - /* get methods */ + /*--- get methods ---*/ {"getPages", getNumberOfPages}, {"getCurrentPage", getCurrentPage}, - {"getPos", getPos}, - {"getPosPercent", getPosPercent}, + {"getPageFromXPointer", getPageFromXPointer}, + {"getCurrentPos", getCurrentPos}, + {"getCurrentPercent", getCurrentPercent}, + {"getXPointer", getXPointer}, {"getFullHeight", getFullHeight}, {"getToc", getTableOfContent}, - /* set methods */ + /*--- set methods ---*/ {"setFontFace", setFontFace}, - /* control methods */ + /* --- control methods ---*/ {"gotoPage", gotoPage}, {"gotoPercent", gotoPercent}, {"gotoPos", gotoPos}, + {"gotoXPointer", gotoXPointer}, {"zoomFont", zoomFont}, {"toggleFontBolder", toggleFontBolder}, + //{"cursorLeft", cursorLeft}, + //{"cursorRight", cursorRight}, {"drawCurrentPage", drawCurrentPage}, {"close", closeDocument}, {"__gc", closeDocument}, diff --git a/crereader.lua b/crereader.lua index 9b5358765..f51adbea7 100644 --- a/crereader.lua +++ b/crereader.lua @@ -3,7 +3,7 @@ require "inputbox" require "selectmenu" CREReader = UniReader:new{ - pos = 0, + pos = nil, percent = 0, gamma_index = 15, @@ -33,6 +33,9 @@ function CREReader:open(filename) return true end +---------------------------------------------------- +-- setting related methods +---------------------------------------------------- function CREReader:loadSpecialSettings() local font_face = self.settings:readSetting("font_face") self.font_face = font_face or "FreeSerif" @@ -46,7 +49,7 @@ end function CREReader:getLastPageOrPos() local last_percent = self.settings:readSetting("last_percent") if last_percent then - return (last_percent * self.doc:getFullHeight()) / 10000 + return math.floor((last_percent * self.doc:getFullHeight()) / 10000) else return 0 end @@ -61,24 +64,40 @@ 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:addJump(pos, notes) - return +function CREReader:redrawCurrentPage() + self:goto(self.pos) end -function CREReader:goto(pos) - local pos = math.min(pos, self.doc:getFullHeight()) - pos = math.max(pos, 0) +---------------------------------------------------- +-- goto related methods +---------------------------------------------------- +function CREReader:goto(pos, pos_type) + local prev_xpointer = self.doc:getXPointer() + 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()) + 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(self.percent) + self:addJump(prev_xpointer) end - self.doc:gotoPos(pos) self.doc:drawCurrentPage(self.nulldc, fb.bb) if self.rcount == self.rcountmax then @@ -93,13 +112,65 @@ function CREReader:goto(pos) self.pos = pos self.pageno = self.doc:getCurrentPage() - self.percent = self.doc:getPosPercent() + self.percent = self.doc:getCurrentPercent() end -function CREReader:redrawCurrentPage() - self:goto(self.pos) +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() + return self.pos + height - self.pan_overlap_vertical +end + +function CREReader:prevView() + return self.pos - 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 = height - 50 @@ -109,7 +180,7 @@ function CREReader:_drawReadingInfo() ypos = ypos + 15 local face, fhash = Font:getFaceAndHash(22) - local cur_section = self:getTocTitleByPage(self.pos) + local cur_section = self:getTocTitleOfCurrentPage() if cur_section ~= "" then cur_section = "Section: "..cur_section end @@ -121,13 +192,7 @@ function CREReader:_drawReadingInfo() 5, 4, load_percent/100, 8) end -function CREReader:nextView() - return self.pos + height - self.pan_overlap_vertical -end -function CREReader:prevView() - return self.pos - height + self.pan_overlap_vertical -end function CREReader:adjustCreReaderCommands() -- delete commands @@ -204,6 +269,14 @@ function CREReader:adjustCreReaderCommands() cr:redrawCurrentPage() 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) diff --git a/unireader.lua b/unireader.lua index 4d010de80..f46f85d57 100644 --- a/unireader.lua +++ b/unireader.lua @@ -563,22 +563,27 @@ function UniReader:show(no) self.slot_visible = slot; end +function UniReader:isSamePage(p1, p2) + return p1 == p2 +end + --[[ @ pageno is the page you want to add to jump_stack + NOTE: for CREReader, pageno refers to xpointer --]] function UniReader:addJump(pageno, notes) local jump_item = nil local notes_to_add = notes if not notes_to_add then -- no notes given, auto generate from Toc entry - notes_to_add = self:getTocTitleByPage(self.pageno) + notes_to_add = self:getTocTitleOfCurrentPage() if notes_to_add ~= "" then notes_to_add = "in "..notes_to_add end end -- move pageno page to jump_stack top if already in for _t,_v in ipairs(self.jump_stack) do - if _v.page == pageno then + if self:isSamePage(_v.page, pageno) then jump_item = _v table.remove(self.jump_stack, _t) -- if original notes is not empty, probably defined by users, @@ -762,29 +767,38 @@ function UniReader:getTocTitleByPage(pageno) return self:cleanUpTocTitle(pre_entry.title) end +function UniReader:getTocTitleOfCurrentPage() + return self:getTocTitleByPage(self.pageno) +end + +function UniReader:gotoTocEntry(entry) + self:goto(entry.page) +end + function UniReader:showToc() if not self.toc then - -- build toc when needed. + -- build toc if needed. self:fillToc() end - local menu_items = {} - local filtered_toc = {} + -- build menu items + local menu_items = {} for k,v in ipairs(self.toc) do table.insert(menu_items, (" "):rep(v.depth-1)..self:cleanUpTocTitle(v.title)) - table.insert(filtered_toc,v.page) end + toc_menu = SelectMenu:new{ menu_title = "Table of Contents", item_array = menu_items, no_item_msg = "This document does not have a Table of Contents.", } item_no = toc_menu:choose(0, fb.bb:getHeight()) + if item_no then - self:goto(filtered_toc[item_no]) + self:gotoTocEntry(self.toc[item_no]) else - self:goto(self.pageno) + self:redrawCurrentPage() end end @@ -849,7 +863,7 @@ function UniReader:_drawReadingInfo() local ypos = height - 50 fb.bb:paintRect(0, ypos, width, 50, 0) ypos = ypos + 15 - local cur_section = self:getTocTitleByPage(self.pageno) + local cur_section = self:getTocTitleOfCurrentPage() if cur_section ~= "" then cur_section = "Section: "..cur_section end From 4631fe492aab2456e3397f2766cd76b0a36eb8ec Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 7 Apr 2012 23:03:29 +0800 Subject: [PATCH 097/183] fix: add jump feature --- crereader.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crereader.lua b/crereader.lua index f51adbea7..698a0d2b5 100644 --- a/crereader.lua +++ b/crereader.lua @@ -269,6 +269,12 @@ function CREReader:adjustCreReaderCommands() 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) From 245247b2d17549a8acb4737160061ef560345c1d Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 7 Apr 2012 23:28:56 +0800 Subject: [PATCH 098/183] mod: use redrawCurrentPage() for all page redraw different readers might have different way to redraw pages, so I added this abstract method --- unireader.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/unireader.lua b/unireader.lua index d9fa01a5e..c0a3d42d5 100644 --- a/unireader.lua +++ b/unireader.lua @@ -707,14 +707,14 @@ end function UniReader:modifyGamma(factor) print("modifyGamma, gamma="..self.globalgamma.." factor="..factor) self.globalgamma = self.globalgamma * factor; - self:goto(self.pageno) + self:redrawCurrentPage() end -- adjust zoom state and trigger re-rendering function UniReader:setGlobalZoomMode(newzoommode) if self.globalzoommode ~= newzoommode then self.globalzoommode = newzoommode - self:goto(self.pageno) + self:redrawCurrentPage() end end @@ -723,13 +723,13 @@ function UniReader:setGlobalZoom(zoom) if self.globalzoom ~= zoom then self.globalzoommode = self.ZOOM_BY_VALUE self.globalzoom = zoom - self:goto(self.pageno) + self:redrawCurrentPage() end end function UniReader:setRotate(rotate) self.globalrotate = rotate - self:goto(self.pageno) + self:redrawCurrentPage() end -- @ orien: 1 for clockwise rotate, -1 for anti-clockwise @@ -737,7 +737,7 @@ function UniReader:screenRotate(orien) Screen:screenRotate(orien) width, height = fb:getSize() self:clearCache() - self:goto(self.pageno) + self:redrawCurrentPage() end function UniReader:cleanUpTocTitle(title) @@ -820,7 +820,7 @@ function UniReader:showJumpStack() local jump_item = self.jump_stack[item_no] self:goto(jump_item.page) else - self:goto(self.pageno) + self:redrawCurrentPage() end end @@ -1075,7 +1075,7 @@ function UniReader:addAllCommands() "show help page", function(unireader) HelpPage:show(0,height,unireader.commands) - unireader:goto(unireader.pageno) + unireader:redrawCurrentPage() end) self.commands:add(KEY_T,nil,"T", "show table of content", @@ -1244,7 +1244,7 @@ function UniReader:addAllCommands() end if old_offset_x ~= unireader.offset_x or old_offset_y ~= unireader.offset_y then - unireader:goto(unireader.pageno) + unireader:redrawCurrentPage() end end end) @@ -1262,7 +1262,7 @@ function UniReader:addAllCommands() os.execute("sleep 1") os.execute("killall -stop cvm") fb:setOrientation(Screen.kpv_rotation_mode) - unireader:goto(unireader.pageno) + unireader:redrawCurrentPage() end) print("## defined commands "..dump(self.commands.map)) end From f926a12b73957ece5048bdef3d9b523b7c7fb88b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 8 Apr 2012 09:58:51 +0800 Subject: [PATCH 099/183] fix: only copy css files from data directory when building customupdate refer to #77 --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8d9ca85a5..eefab7d3b 100644 --- a/Makefile +++ b/Makefile @@ -195,7 +195,8 @@ customupdate: kpdfview file kpdfview | grep ARM || exit 1 mkdir $(INSTALL_DIR) cp -p README.TXT COPYING kpdfview slider_watcher *.lua $(INSTALL_DIR) - cp -rpL data $(INSTALL_DIR) + mkdir $(INSTALL_DIR)/data + cp -rpL data/*.css $(INSTALL_DIR)/data cp -rp fonts $(INSTALL_DIR) zip -r kindlepdfviewer-$(VERSION).zip $(INSTALL_DIR) launchpad/ rm -Rf $(INSTALL_DIR) From b8b1bc19fd82b295d7f65a5336f31dccba8fafcf Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 8 Apr 2012 11:21:02 +0800 Subject: [PATCH 100/183] fix: wait for more time instead of suspending framework refer to discussion in #88 --- unireader.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unireader.lua b/unireader.lua index c0a3d42d5..4c1353a09 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1254,13 +1254,13 @@ function UniReader:addAllCommands() function(unireader) Screen.kpv_rotation_mode = Screen.cur_rotation_mode fb:setOrientation(Screen.native_rotation_mode) - os.execute("killall -cont cvm") + --os.execute("killall -cont cvm") end) self.commands:add(KEY_OUTOF_SCREEN_SAVER,nil,"slider", "toggle screen saver", function(unireader) - os.execute("sleep 1") - os.execute("killall -stop cvm") + os.execute("sleep 3") + --os.execute("killall -stop cvm") fb:setOrientation(Screen.kpv_rotation_mode) unireader:redrawCurrentPage() end) From 7f0faa20bbeeffa6b63c531d2d0a2fd0593c0a4f Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 8 Apr 2012 11:57:25 +0800 Subject: [PATCH 101/183] mod: rewrite filesearcher with command module --- filesearcher.lua | 257 +++++++++++++++++++++++++++++------------------ selectmenu.lua | 51 +++++----- 2 files changed, 183 insertions(+), 125 deletions(-) diff --git a/filesearcher.lua b/filesearcher.lua index 31ec6e371..54fa5ee54 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -85,42 +85,143 @@ 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:choose(ypos, height, keywords) - local perpage = math.floor(height / self.spacing) - 2 - local pagedirty = true - local markerdirty = false +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: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_PGFWD, 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 = keywords + keywords = InputBox:input(height-100, 100, "Search:", old_keywords) + if keywords then + self:setSearchResult(keywords) + else + 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) + 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 + 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 + + -- rotation mode might be changed while reading, so + -- record height_percent here + local height_percent = height/fb.bb:getHeight() + openFile(file_full_path) + + --reset height and item index if screen has been rotated + local old_perpage = self.perpage + height = math.floor(fb.bb:getHeight()*height_percent) + self.perpage = math.floor(height / self.spacing) - 2 + self.current = (old_perpage * (self.page - 1) + + self.current) % self.perpage + self.page = math.floor(self.items / 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(ypos, height, keywords) + self.perpage = math.floor(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. @@ -133,8 +234,8 @@ function FileSearcher:choose(ypos, height, keywords) local tface, tfhash = Font:getFaceAndHash(25, Font.tfont) local fface, ffhash = Font:getFaceAndHash(16, Font.ffont) - if pagedirty then - markerdirty = true + if self.pagedirty then + self.markerdirty = true fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0) -- draw menu title @@ -149,10 +250,10 @@ function FileSearcher:choose(ypos, height, keywords) "Sorry, no match found.", true) renderUtf8Text(fb.bb, 20, y + self.spacing, cface, cfhash, "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, @@ -162,15 +263,15 @@ function FileSearcher:choose(ypos, height, keywords) end -- draw footer - y = ypos + self.title_H + (self.spacing * perpage) + self.foot_H + y = ypos + self.title_H + (self.spacing * self.perpage) + self.foot_H x = (fb.bb:getWidth() / 2) - 50 - all_page = (math.floor(self.items / perpage)+1) + all_page = (math.floor(self.items / self.perpage)+1) renderUtf8Text(fb.bb, x, y, fface, ffhash, "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) @@ -180,85 +281,41 @@ function FileSearcher:choose(ypos, height, keywords) -- 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 + if not self.pagedirty then fb:refresh(1, 30, 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() 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 -- EOF if + end -- EOF while + return nil end diff --git a/selectmenu.lua b/selectmenu.lua index c4ce67f45..9a7b6ec1e 100644 --- a/selectmenu.lua +++ b/selectmenu.lua @@ -85,8 +85,8 @@ function SelectMenu:addAllCommands() sm.current = sm.current - 1 sm.markerdirty = true end - end) - + end + ) self.commands:add(KEY_FW_DOWN, nil, "", "next item", function(sm) @@ -103,8 +103,8 @@ function SelectMenu:addAllCommands() sm.markerdirty = true end end - end) - + end + ) self.commands:add({KEY_PGFWD, KEY_LPGFWD}, nil, "", "next page", function(sm) @@ -118,8 +118,8 @@ function SelectMenu:addAllCommands() sm.current = sm.items - (sm.page - 1) * sm.perpage sm.markerdirty = true end - end) - + end + ) self.commands:add({KEY_PGBCK, KEY_LPGBCK}, nil, "", "previous page", function(sm) @@ -130,8 +130,8 @@ function SelectMenu:addAllCommands() sm.current = 1 sm.markerdirty = true end - end) - + end + ) self.commands:add(KEY_FW_PRESS, nil, "", "select menu item", function(sm) @@ -143,8 +143,8 @@ function SelectMenu:addAllCommands() + sm.current) end end - end) - + end + ) local KEY_Q_to_E = {} for i = KEY_Q, KEY_P do table.insert(KEY_Q_to_E, Keydef:new(i, nil, "")) @@ -154,8 +154,8 @@ function SelectMenu:addAllCommands() function(sm, keydef) sm.selected_item = sm:getItemIndexByShortCut( sm.item_shortcuts[ keydef.keycode - KEY_Q + 1 ], sm.perpage) - end) - + end + ) local KEY_A_to_L = {} for i = KEY_A, KEY_L do table.insert(KEY_A_to_L, Keydef:new(i, nil, "")) @@ -165,8 +165,8 @@ function SelectMenu:addAllCommands() function(sm, keydef) sm.selected_item = sm:getItemIndexByShortCut( sm.item_shortcuts[ keydef.keycode - KEY_A + 11 ], sm.perpage) - end) - + end + ) local KEY_Z_to_M = {} for i = KEY_Z, KEY_M do table.insert(KEY_Z_to_M, Keydef:new(i, nil, "")) @@ -176,38 +176,39 @@ function SelectMenu:addAllCommands() function(sm, keydef) sm.selected_item = sm:getItemIndexByShortCut( sm.item_shortcuts[ keydef.keycode - KEY_Z + 21 ], sm.perpage) - end) - + 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) - + 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) - + 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) - + 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) - + end + ) self.commands:add(KEY_BACK, nil, "", "Exit menu", function(sm) return "break" - end) + end + ) end function SelectMenu:clearCommands() From 709e7853e4f4b74b6a3fa64eac02300b205ee6e7 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 8 Apr 2012 12:46:33 +0800 Subject: [PATCH 102/183] mod: move slider event handling to global command module Since this event must be handled in all UIs, it is better to register it on command object construction. --- commands.lua | 19 +++++++++++++++++++ screen.lua | 26 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/commands.lua b/commands.lua index 1d7064b03..abdb19cfe 100644 --- a/commands.lua +++ b/commands.lua @@ -162,5 +162,24 @@ function Commands:new(obj) end 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() + os.execute("sleep 3") + --os.execute("killall -stop cvm") + fb:setOrientation(Screen.kpv_rotation_mode) + Screen:resotreFromSavedBB() + fb:refresh(0) + end + ) return obj end diff --git a/screen.lua b/screen.lua index 6d6022498..0ac639044 100644 --- a/screen.lua +++ b/screen.lua @@ -44,6 +44,8 @@ Screen = { -- 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 @@ -74,3 +76,27 @@ function Screen:updateRotationMode() end end +function Screen:saveCurrentBB() + 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:resotreFromSavedBB() + self:restoreFromBB(self.saved_bb) +end + +function Screen:getCurrentScreenBB() + local bb = Blitbuffer.new(width, height) + bb:blitFullFrom(fb.bb) + return bb +end + +function Screen:restoreFromBB(bb) + fb.bb:blitFullFrom(bb) +end From 5f7f9a2a83f7135cc65d31b64a70f3ac2680c14c Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 8 Apr 2012 12:49:23 +0800 Subject: [PATCH 103/183] fix: remove slider event handling in unireader Slider event handling has been moved to commands module, refer to previous commit. --- unireader.lua | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/unireader.lua b/unireader.lua index 4c1353a09..bc5294a8e 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1249,20 +1249,5 @@ function UniReader:addAllCommands() end end) -- end panning - self.commands:add(KEY_INTO_SCREEN_SAVER,nil,"slider", - "toggle screen saver", - function(unireader) - Screen.kpv_rotation_mode = Screen.cur_rotation_mode - fb:setOrientation(Screen.native_rotation_mode) - --os.execute("killall -cont cvm") - end) - self.commands:add(KEY_OUTOF_SCREEN_SAVER,nil,"slider", - "toggle screen saver", - function(unireader) - os.execute("sleep 3") - --os.execute("killall -stop cvm") - fb:setOrientation(Screen.kpv_rotation_mode) - unireader:redrawCurrentPage() - end) print("## defined commands "..dump(self.commands.map)) end From 7fdec8a9807efe1621ec171540c882aa494ddc80 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 8 Apr 2012 14:31:23 +0800 Subject: [PATCH 104/183] fix: handle screen resolution correctly in filesearcher --- filechooser.lua | 2 +- filesearcher.lua | 38 ++++++++++++++++++-------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/filechooser.lua b/filechooser.lua index cb5a5aaf5..301cd052b 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -197,7 +197,7 @@ function FileChooser:choose(ypos, height) --]] return nil, function() FileSearcher:init( self.path ) - FileSearcher:choose(ypos, height, keywords) + FileSearcher:choose(keywords) end end pagedirty = true diff --git a/filesearcher.lua b/filesearcher.lua index 54fa5ee54..0a86af39a 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -147,7 +147,7 @@ function FileSearcher:addAllCommands() end end ) - self.commands:add(KEY_PGFWD, nil, "<", + self.commands:add(KEY_PGBCK, nil, "<", "previous page", function(self) if self.page > 1 then @@ -199,12 +199,10 @@ function FileSearcher:addAllCommands() openFile(file_full_path) --reset height and item index if screen has been rotated - local old_perpage = self.perpage - height = math.floor(fb.bb:getHeight()*height_percent) + local item_no = self.perpage * (self.page - 1) + self.current self.perpage = math.floor(height / self.spacing) - 2 - self.current = (old_perpage * (self.page - 1) + - self.current) % self.perpage - self.page = math.floor(self.items / self.perpage) + 1 + self.current = item_no % self.perpage + self.page = math.floor(item_no / self.perpage) + 1 self.pagedirty = true end @@ -217,7 +215,7 @@ function FileSearcher:addAllCommands() ) end -function FileSearcher:choose(ypos, height, keywords) +function FileSearcher:choose(keywords) self.perpage = math.floor(height / self.spacing) - 2 self.pagedirty = true self.markerdirty = false @@ -236,16 +234,16 @@ function FileSearcher:choose(ypos, height, keywords) if self.pagedirty then self.markerdirty = true - fb.bb:paintRect(0, ypos, fb.bb:getWidth(), height, 0) + fb.bb:paintRect(0, 0, width, height, 0) -- draw menu title - renderUtf8Text(fb.bb, 30, ypos + self.title_H, tface, tfhash, + renderUtf8Text(fb.bb, 30, 0 + self.title_H, tface, tfhash, "Search Result for: "..keywords, true) -- draw results local c if self.items == 0 then -- nothing found - y = ypos + self.title_H + self.spacing * 2 + y = self.title_H + self.spacing * 2 renderUtf8Text(fb.bb, 20, y, cface, cfhash, "Sorry, no match found.", true) renderUtf8Text(fb.bb, 20, y + self.spacing, cface, cfhash, @@ -255,7 +253,7 @@ function FileSearcher:choose(ypos, height, keywords) 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) + y = self.title_H + (self.spacing * c) renderUtf8Text(fb.bb, 50, y, cface, cfhash, self.result[i].name, true) end @@ -263,8 +261,8 @@ function FileSearcher:choose(ypos, height, keywords) end -- draw footer - y = ypos + self.title_H + (self.spacing * self.perpage) + self.foot_H - x = (fb.bb:getWidth() / 2) - 50 + y = self.title_H + (self.spacing * self.perpage) + self.foot_H + x = (width / 2) - 50 all_page = (math.floor(self.items / self.perpage)+1) renderUtf8Text(fb.bb, x, y, fface, ffhash, "Page "..self.page.." of "..all_page, true) @@ -273,23 +271,23 @@ function FileSearcher:choose(ypos, height, keywords) 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, width - 60, 3, 0) + fb:refresh(1, 30, y, 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) + y = self.title_H + (self.spacing * self.current) + 10 + fb.bb:paintRect(30, y, width - 60, 3, 15) if not self.pagedirty then - fb:refresh(1, 30, y, fb.bb:getWidth() - 60, 3) + fb:refresh(1, 30, y, width - 60, 3) end self.oldcurrent = self.current self.markerdirty = false end if self.pagedirty then - fb:refresh(0, 0, ypos, fb.bb:getWidth(), height) + fb:refresh(0, 0, 0, width, height) self.pagedirty = false end From 7886cb5215d42f3676e95effc2d30983a264455b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 8 Apr 2012 17:16:48 +0800 Subject: [PATCH 105/183] mod: kill slider_watcher on kpv exit --- launchpad/kpdf.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index e43bc5c04..75f8f4875 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -15,4 +15,5 @@ killall slider_watcher ./reader.lua $1 2> /mnt/us/kindlepdfviewer/crash.log killall -cont cvm +killall slider_watcher echo 1 > /proc/eink_fb/update_display From 4e542f7d4f7289112fa2c601aaf007918fd6a6a7 Mon Sep 17 00:00:00 2001 From: HW Date: Sun, 8 Apr 2012 21:16:07 +0200 Subject: [PATCH 106/183] fixed small bug in panning to the bottom --- crereader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crereader.lua b/crereader.lua index 698a0d2b5..f42c4a3b8 100644 --- a/crereader.lua +++ b/crereader.lua @@ -85,7 +85,7 @@ function CREReader:goto(pos, pos_type) self.doc:gotoXPointer(pos) pos = self.doc:getCurrentPos() else -- pos_type is PERCENT * 100 - pos = math.min(pos, self.doc:getFullHeight()) + pos = math.min(pos, self.doc:getFullHeight()-height) pos = math.max(pos, 0) self.doc:gotoPos(pos) end From d62e3423a2d0bba9e3e7249cf3ba7fd5def999fc Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 9 Apr 2012 14:06:22 +0800 Subject: [PATCH 107/183] fix: save keywords in filesearcher --- filesearcher.lua | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/filesearcher.lua b/filesearcher.lua index 0a86af39a..da013f386 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -74,6 +74,7 @@ function FileSearcher:setSearchResult(keywords) end end end + self.keywords = keywords self.items = #self.result self.page = 1 self.current = 1 @@ -162,12 +163,13 @@ function FileSearcher:addAllCommands() self.commands:add(KEY_S, nil, "S", "invoke search inputbox", function(self) - old_keywords = keywords - keywords = InputBox:input(height-100, 100, "Search:", old_keywords) - if keywords then - self:setSearchResult(keywords) + old_keywords = self.keywords + self.keywords = InputBox:input(height-100, 100, + "Search:", old_keywords) + if self.keywords then + self:setSearchResult(self.keywords) else - keywords = old_keywords + self.keywords = old_keywords end self.pagedirty = true end @@ -238,7 +240,7 @@ function FileSearcher:choose(keywords) -- draw menu title renderUtf8Text(fb.bb, 30, 0 + self.title_H, tface, tfhash, - "Search Result for: "..keywords, true) + "Search Result for: "..self.keywords, true) -- draw results local c From f0042714d90d3775196def8a99f0642b5c2c3134 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 9 Apr 2012 15:06:52 +0800 Subject: [PATCH 108/183] fix: correct upper bound for pos inside crereader --- crereader.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crereader.lua b/crereader.lua index 698a0d2b5..880f99083 100644 --- a/crereader.lua +++ b/crereader.lua @@ -85,7 +85,7 @@ function CREReader:goto(pos, pos_type) self.doc:gotoXPointer(pos) pos = self.doc:getCurrentPos() else -- pos_type is PERCENT * 100 - pos = math.min(pos, self.doc:getFullHeight()) + pos = math.min(pos, self.doc:getFullHeight() - height) pos = math.max(pos, 0) self.doc:gotoPos(pos) end @@ -111,6 +111,7 @@ function CREReader:goto(pos, pos_type) end self.pos = pos + print("------", self.pos) self.pageno = self.doc:getCurrentPage() self.percent = self.doc:getCurrentPercent() end From ecd56a3745d6f87e0f06992d42f40ac4b11d71d4 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 9 Apr 2012 15:42:19 +0800 Subject: [PATCH 109/183] mod: prefix global variables width and height with G_ --- crereader.lua | 16 +++++++++------- djvureader.lua | 8 ++++---- filesearcher.lua | 11 ++++------- helppage.lua | 2 +- pdfreader.lua | 2 +- reader.lua | 4 ++-- rendertext_example.lua | 2 +- screen.lua | 4 +++- unireader.lua | 27 +++++++++++++++++---------- 9 files changed, 42 insertions(+), 34 deletions(-) diff --git a/crereader.lua b/crereader.lua index 880f99083..81e55dc73 100644 --- a/crereader.lua +++ b/crereader.lua @@ -25,7 +25,7 @@ function CREReader:open(filename) end local style_sheet = "./data/"..file_type..".css" ok, self.doc = pcall(cre.openDocument, filename, style_sheet, - width, height) + G_width, G_height) if not ok then return false, self.doc -- will contain error message end @@ -81,6 +81,8 @@ end ---------------------------------------------------- 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() @@ -125,11 +127,11 @@ function CREReader:gotoTocEntry(entry) end function CREReader:nextView() - return self.pos + height - self.pan_overlap_vertical + return self.pos + G_height - self.pan_overlap_vertical end function CREReader:prevView() - return self.pos - height + self.pan_overlap_vertical + return self.pos - G_height + self.pan_overlap_vertical end ---------------------------------------------------- @@ -174,10 +176,10 @@ end ---------------------------------------------------- -- used in CREReader:showMenu() function CREReader:_drawReadingInfo() - local ypos = height - 50 + local ypos = G_height - 50 local load_percent = self.percent/100 - fb.bb:paintRect(0, ypos, width, 50, 0) + fb.bb:paintRect(0, ypos, G_width, 50, 0) ypos = ypos + 15 local face, fhash = Font:getFaceAndHash(22) @@ -189,7 +191,7 @@ function CREReader:_drawReadingInfo() "Position: "..load_percent.."%".." "..cur_section, true) ypos = ypos + 15 - blitbuffer.progressBar(fb.bb, 10, ypos, width-20, 15, + blitbuffer.progressBar(fb.bb, 10, ypos, G_width - 20, 15, 5, 4, load_percent/100, 8) end @@ -254,7 +256,7 @@ function CREReader:adjustCreReaderCommands() item_array = face_list, } - local item_no = fonts_menu:choose(0, height) + 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]) diff --git a/djvureader.lua b/djvureader.lua index c9ec0cdf4..460b2167d 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -66,13 +66,13 @@ function DJVUReader:_isEntireWordInScreenHeightRange(w) (self.cur_full_height - (w.y1 * self.globalzoom) >= -self.offset_y) and (self.cur_full_height - (w.y0 * self.globalzoom) <= - -self.offset_y + height) + -self.offset_y + G_height) end function DJVUReader:_isEntireWordInScreenWidthRange(w) return (w ~= nil) and (w.x0 * self.globalzoom >= -self.offset_x) and - (w.x1 * self.globalzoom <= -self.offset_x + width) + (w.x1 * self.globalzoom <= -self.offset_x + G_width) end -- make sure at least part of the word can be seen in screen @@ -81,9 +81,9 @@ function DJVUReader:_isWordInScreenRange(w) (self.cur_full_height - (w.y0 * self.globalzoom) >= -self.offset_y) and (self.cur_full_height - (w.y1 * self.globalzoom) <= - -self.offset_y + height) and + -self.offset_y + G_height) and (w.x1 * self.globalzoom >= -self.offset_x) and - (w.x0 * self.globalzoom <= -self.offset_x + width) + (w.x0 * self.globalzoom <= -self.offset_x + G_width) end function DJVUReader:toggleTextHighLight(word_list) diff --git a/filesearcher.lua b/filesearcher.lua index da013f386..415dac45f 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -164,7 +164,7 @@ function FileSearcher:addAllCommands() "invoke search inputbox", function(self) old_keywords = self.keywords - self.keywords = InputBox:input(height-100, 100, + self.keywords = InputBox:input(G_height - 100, 100, "Search:", old_keywords) if self.keywords then self:setSearchResult(self.keywords) @@ -181,7 +181,7 @@ function FileSearcher:addAllCommands() menu_title = "Fonts Menu", item_array = Font.fonts, } - local re = fonts_menu:choose(0, height) + local re = fonts_menu:choose(0, G_height) if re then Font.cfont = Font.fonts[re] Font:update() @@ -195,14 +195,10 @@ function FileSearcher:addAllCommands() file_entry = self.result[self.perpage*(self.page-1)+self.current] file_full_path = file_entry.dir .. "/" .. file_entry.name - -- rotation mode might be changed while reading, so - -- record height_percent here - local height_percent = height/fb.bb:getHeight() 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(height / self.spacing) - 2 + self.perpage = math.floor(G_height / self.spacing) - 2 self.current = item_no % self.perpage self.page = math.floor(item_no / self.perpage) + 1 @@ -218,6 +214,7 @@ function FileSearcher:addAllCommands() end function FileSearcher:choose(keywords) + local width, height = G_width, G_height self.perpage = math.floor(height / self.spacing) - 2 self.pagedirty = true self.markerdirty = false diff --git a/helppage.lua b/helppage.lua index 33ba5e9a9..cedb17906 100644 --- a/helppage.lua +++ b/helppage.lua @@ -30,7 +30,7 @@ HelpPage.hface, HelpPage.hfhash = Font:getFaceAndHash(HelpPage.hfsize, "sans") HelpPage.ffsize = 15 HelpPage.fface, HelpPage.ffhash = Font:getFaceAndHash(HelpPage.ffsize, "sans") -function HelpPage:show(ypos, height,commands) +function HelpPage:show(ypos, height, commands) self.commands = {} self.items = 0 local keys = {} diff --git a/pdfreader.lua b/pdfreader.lua index 9413e0127..334a69d85 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -13,7 +13,7 @@ function PDFReader:open(filename) 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 diff --git a/reader.lua b/reader.lua index 193eb768d..091f6f37b 100755 --- a/reader.lua +++ b/reader.lua @@ -121,7 +121,7 @@ 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() Screen.native_rotation_mode = Screen.cur_rotation_mode @@ -146,7 +146,7 @@ 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 diff --git a/rendertext_example.lua b/rendertext_example.lua index 12dbf524d..3c208b8f5 100755 --- a/rendertext_example.lua +++ b/rendertext_example.lua @@ -3,7 +3,7 @@ require "rendertext" require "graphics" fb = einkfb.open("/dev/fb0") -width, height = fb:getSize() +G_width, G_height = fb:getSize() print("open") diff --git a/screen.lua b/screen.lua index 0ac639044..25edb1096 100644 --- a/screen.lua +++ b/screen.lua @@ -77,6 +77,8 @@ function Screen:updateRotationMode() 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 @@ -92,7 +94,7 @@ function Screen:resotreFromSavedBB() end function Screen:getCurrentScreenBB() - local bb = Blitbuffer.new(width, height) + local bb = Blitbuffer.new(G_width, G_height) bb:blitFullFrom(fb.bb) return bb end diff --git a/unireader.lua b/unireader.lua index bc5294a8e..ad14cd5d2 100644 --- a/unireader.lua +++ b/unireader.lua @@ -250,6 +250,7 @@ function UniReader:drawOrCache(no, preCache) -- ideally, this should be factored out and only be called when needed (TODO) local ok, page = pcall(self.doc.openPage, self.doc, no) + local width, height = G_width, G_height if not ok then -- TODO: error handling return nil @@ -362,6 +363,7 @@ end function UniReader:setzoom(page, preCache) local dc = DrawContext.new() local pwidth, pheight = page:getSize(self.nulldc) + local width, height = G_width, G_height print("# page::getSize "..pwidth.."*"..pheight); local x0, y0, x1, y1 = page:getUsedBBox() if x0 == 0.01 and y0 == 0.01 and x1 == -0.01 and y1 == -0.01 then @@ -517,6 +519,8 @@ end -- render and blit a page function UniReader:show(no) local pagehash, offset_x, offset_y = self:drawOrCache(no) + local width, height = G_width, G_height + if not pagehash then return end @@ -662,7 +666,8 @@ function UniReader:nextView() pageno = pageno + 1 else -- goto next view of current page - self.offset_y = self.offset_y - height + self.pan_overlap_vertical + self.offset_y = self.offset_y - G_height + + self.pan_overlap_vertical end else -- not in fit to content width pan mode, just do a page turn @@ -688,7 +693,8 @@ function UniReader:prevView() pageno = pageno - 1 else -- goto previous view of current page - self.offset_y = self.offset_y + height - self.pan_overlap_vertical + self.offset_y = self.offset_y + G_height + - self.pan_overlap_vertical end else -- not in fit to content width pan mode, just do a page turn @@ -735,7 +741,8 @@ end -- @ orien: 1 for clockwise rotate, -1 for anti-clockwise function UniReader:screenRotate(orien) Screen:screenRotate(orien) - width, height = fb:getSize() + -- update global width and height variable + G_width, G_height = fb:getSize() self:clearCache() self:redrawCurrentPage() end @@ -849,7 +856,7 @@ end -- used in UniReader:showMenu() function UniReader:_drawReadingInfo() - local ypos = height - 50 + local width, height = G_width, G_height local load_percent = (self.pageno / self.doc:getPages()) local face, fhash = Font:getFaceAndHash(22) @@ -1060,7 +1067,7 @@ function UniReader:addAllCommands() self.commands:add(KEY_G,nil,"G", "goto page", function(unireader) - local page = InputBox:input(height-100, 100, "Page:") + local page = InputBox:input(G_height-100, 100, "Page:") -- convert string to number if not pcall(function () page = page + 0 end) then page = unireader.pageno @@ -1074,7 +1081,7 @@ function UniReader:addAllCommands() self.commands:add(KEY_H,nil,"H", "show help page", function(unireader) - HelpPage:show(0,height,unireader.commands) + HelpPage:show(0, G_height, unireader.commands) unireader:redrawCurrentPage() end) self.commands:add(KEY_T,nil,"T", @@ -1130,8 +1137,8 @@ function UniReader:addAllCommands() local bbox = {} bbox["x0"] = - unireader.offset_x / unireader.globalzoom bbox["y0"] = - unireader.offset_y / unireader.globalzoom - bbox["x1"] = bbox["x0"] + width / unireader.globalzoom - bbox["y1"] = bbox["y0"] + height / unireader.globalzoom + bbox["x1"] = bbox["x0"] + G_width / unireader.globalzoom + bbox["y1"] = bbox["y0"] + G_height / unireader.globalzoom bbox.pan_x = unireader.pan_x bbox.pan_y = unireader.pan_y unireader.bbox[unireader.pageno] = bbox @@ -1176,8 +1183,8 @@ function UniReader:addAllCommands() x = unireader.shift_x / 5 y = unireader.shift_y / 5 elseif unireader.pan_by_page then - x = width; - y = height - unireader.pan_overlap_vertical; -- overlap for lines which didn't fit + x = G_width; + y = G_height - unireader.pan_overlap_vertical; -- overlap for lines which didn't fit else x = unireader.shift_x y = unireader.shift_y From ff60a41604cbcac27940378efa4a0d0245728424 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 9 Apr 2012 17:17:23 +0800 Subject: [PATCH 110/183] mod: rewrite inputbox with commands --- inputbox.lua | 202 ++++++++++++++++++++++++--------------------------- 1 file changed, 94 insertions(+), 108 deletions(-) diff --git a/inputbox.lua b/inputbox.lua index abbaeb687..fb942f1bf 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -25,6 +25,7 @@ InputBox = { fhash = "m25", fheight = 25, fwidth = 15, + commands = nil, } function InputBox:refreshText() @@ -109,6 +110,8 @@ end ---------------------------------------------------------------------- function InputBox:input(ypos, height, title, d_text) -- do some initilization + self:addAllCommands() + self.ypos = ypos self.h = height self.input_start_y = ypos + 35 self.input_cur_x = self.input_start_x @@ -138,116 +141,20 @@ function InputBox:input(ypos, height, title, d_text) local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - --local secs, usecs = util.gettime() - if ev.code == KEY_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_FW_LEFT then - if (self.cursor.x_pos + 3) > self.input_start_x then - self.cursor:moveHorizontalAndDraw(-self.fwidth) - fb:refresh(1, self.input_start_x-5, ypos, - self.input_slot_w, h) - end - elseif ev.code == KEY_FW_RIGHT then - if (self.cursor.x_pos + 3) < self.input_cur_x then - self.cursor:moveHorizontalAndDraw(self.fwidth) - fb:refresh(1,self.input_start_x-5, ypos, - self.input_slot_w, h) - end - 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 - if Keys.shiftmode then - self:clearText() - else - self:delChar() - end - elseif ev.code == KEY_BACK or ev.code == KEY_HOME 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 + break + end end -- if end -- while @@ -255,3 +162,82 @@ function InputBox:input(ypos, height, title, d_text) 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"}, + } + 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 From 81c9ff1cf5648fba7a34a56e73c654cc67385d35 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Mon, 9 Apr 2012 17:38:50 +0800 Subject: [PATCH 111/183] fix: typo in selectmenu.lua --- filesearcher.lua | 4 ++-- selectmenu.lua | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/filesearcher.lua b/filesearcher.lua index 415dac45f..257d96b95 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -312,7 +312,7 @@ function FileSearcher:choose(keywords) print("# selected "..self.selected_item) return self.selected_item end - end -- EOF if - end -- EOF while + end -- if + end -- while true return nil end diff --git a/selectmenu.lua b/selectmenu.lua index 9a7b6ec1e..1a63e3b42 100644 --- a/selectmenu.lua +++ b/selectmenu.lua @@ -145,11 +145,11 @@ function SelectMenu:addAllCommands() end end ) - local KEY_Q_to_E = {} + local KEY_Q_to_P = {} for i = KEY_Q, KEY_P do - table.insert(KEY_Q_to_E, Keydef:new(i, nil, "")) + table.insert(KEY_Q_to_P, Keydef:new(i, nil, "")) end - self.commands:addGroup("Q to E", KEY_Q_to_E, + 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( From 877ecdfb714809a18bf73a66f70f2d984bc1b833 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 12:29:13 +0200 Subject: [PATCH 112/183] switched slider handling to a fork()ed process --- input.c | 94 +++++++++++++++++++++++++++++++++++++++++------ launchpad/kpdf.sh | 12 +----- reader.lua | 15 +------- 3 files changed, 86 insertions(+), 35 deletions(-) diff --git a/input.c b/input.c index 2f7853d2c..60b58afb3 100644 --- a/input.c +++ b/input.c @@ -19,29 +19,95 @@ #include #include #include +#include #include #include "input.h" +#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 findFreeFdSlot() { + int i; + for(i=0; i: %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]; + } + } 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."); @@ -66,7 +132,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 +147,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); } diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index 75f8f4875..c1f42952e 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -1,19 +1,11 @@ #!/bin/sh -SLIDER_EVENT_PIPE="/tmp/event_slider" export LC_ALL="en_US.UTF-8" echo unlock > /proc/keypad echo unlock > /proc/fiveway -cd /mnt/us/kindlepdfviewer/ -# create the named pipe for power slider event -if [ ! -p $SLIDER_EVENT_PIPE ]; then - mkfifo $SLIDER_EVENT_PIPE -fi -killall slider_watcher -./slider_watcher $SLIDER_EVENT_PIPE & +cd /mnt/us/kindlepdfviewer/ +./reader.lua "$1" 2> /mnt/us/kindlepdfviewer/crash.log -./reader.lua $1 2> /mnt/us/kindlepdfviewer/crash.log killall -cont cvm -killall slider_watcher echo 1 > /proc/eink_fb/update_display diff --git a/reader.lua b/reader.lua index 193eb768d..2bb8da7f2 100755 --- a/reader.lua +++ b/reader.lua @@ -69,9 +69,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") @@ -89,22 +86,14 @@ 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") - input.open("/tmp/event_slider") - 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") - input.open("/tmp/event_slider") -- check if we are running on Kindle 3 (additional volume input) local f=lfs.attributes("/dev/input/event2") From b4fa798d9252ec2b16721ee1fdab0cbcd9e60cab Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 12:30:12 +0200 Subject: [PATCH 113/183] device recognition now fully automatic --- util.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/util.c b/util.c index 571792bf4..e8978f350 100644 --- a/util.c +++ b/util.c @@ -46,9 +46,19 @@ 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}, {"utf8charcode", utf8charcode}, + {"isEmulated", isEmulated}, {NULL, NULL} }; From 2d5e398be9328c09846fb962c6481c62d9744979 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 12:30:56 +0200 Subject: [PATCH 114/183] removed obsolete Shift-P-P combination --- launchpad/kpdf.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/launchpad/kpdf.ini b/launchpad/kpdf.ini index 4fd46d72e..870314044 100755 --- a/launchpad/kpdf.ini +++ b/launchpad/kpdf.ini @@ -1,3 +1,2 @@ [Actions] -P P = !/mnt/us/launchpad/kpdf.sh P D = !/mnt/us/launchpad/kpdf.sh /mnt/us/documents From cce3b41d63df5d14b20f7378130f61d54dd86d00 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 12:40:38 +0200 Subject: [PATCH 115/183] don't build slider_watcher for now --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index eefab7d3b..572827216 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \ LUALIB := $(LUADIR)/src/liblua.a -all:kpdfview slider_watcher +all:kpdfview kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) $(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) -lstdc++ \ @@ -194,7 +194,7 @@ customupdate: kpdfview # ensure that build binary is for ARM file kpdfview | grep ARM || exit 1 mkdir $(INSTALL_DIR) - cp -p README.TXT COPYING kpdfview slider_watcher *.lua $(INSTALL_DIR) + cp -p README.TXT COPYING kpdfview *.lua $(INSTALL_DIR) mkdir $(INSTALL_DIR)/data cp -rpL data/*.css $(INSTALL_DIR)/data cp -rp fonts $(INSTALL_DIR) From dbbd6e4c1247e4caeff79245c62f5dd352b17d5b Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 9 Apr 2012 12:59:30 +0200 Subject: [PATCH 116/183] call all target for customupdates and cleanup install dir --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index eefab7d3b..9922eb8f7 100644 --- a/Makefile +++ b/Makefile @@ -190,9 +190,10 @@ 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 + rm -Rf $(INSTALL_DIR) mkdir $(INSTALL_DIR) cp -p README.TXT COPYING kpdfview slider_watcher *.lua $(INSTALL_DIR) mkdir $(INSTALL_DIR)/data From c24d0bd26e2a54e99188749f3ac09782749a1907 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 13:00:02 +0200 Subject: [PATCH 117/183] corrected CFLAGS for some base .c files --- Makefile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 572827216..7bfc78275 100644 --- a/Makefile +++ b/Makefile @@ -98,17 +98,14 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft $(CRENGINELIBS) \ -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 $< -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: %.o: %.c + $(CC) -c $(KPDFREADER_CFLAGS) $(EMU_CFLAGS) -I$(LFSDIR)/src $< -o $@ djvu.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(DJVUDIR)/ $< -o $@ From a824204039a8d3e4e16f54d7e220eb79883c0067 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 16:32:19 +0200 Subject: [PATCH 118/183] fixed missed case for device/emu recognition --- reader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reader.lua b/reader.lua index 114f91570..934c09f2f 100755 --- a/reader.lua +++ b/reader.lua @@ -165,7 +165,7 @@ reader_settings:close() fb:setOrientation(Screen.native_rotation_mode) input.closeAll() -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 From 5d9176907f2c25addb81727f24665df3ae6422cc Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 9 Apr 2012 15:06:05 +0200 Subject: [PATCH 119/183] show_overlap inverts part of page visible in previous view This is nice feature of Sony e-book readers which is very useful when reading since it shows previous part of visible page and provides hint where to continue reading after page refresh --- crereader.lua | 10 ++++++++++ unireader.lua | 23 +++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/crereader.lua b/crereader.lua index 81e55dc73..af3ca86e9 100644 --- a/crereader.lua +++ b/crereader.lua @@ -102,6 +102,14 @@ function CREReader:goto(pos, pos_type) self.doc:drawCurrentPage(self.nulldc, fb.bb) + print("## self.show_overlap "..self.show_overlap) + if self.show_overlap < 0 then + fb.bb:invertRect(0,0, width, -self.show_overlap) + elseif self.show_overlap > 0 then + fb.bb:invertRect(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 @@ -127,10 +135,12 @@ function CREReader:gotoTocEntry(entry) 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 diff --git a/unireader.lua b/unireader.lua index ad14cd5d2..e845bd644 100644 --- a/unireader.lua +++ b/unireader.lua @@ -54,6 +54,7 @@ UniReader = { pan_y = 0, pan_margin = 20, -- horizontal margin for two-column zoom pan_overlap_vertical = 30, + show_overlap = 0, -- the document: doc = nil, @@ -552,6 +553,14 @@ function UniReader:show(no) "width:"..width..", height:"..height) fb.bb:blitFrom(bb, dest_x, dest_y, offset_x, offset_y, width, height) + print("## self.show_overlap "..self.show_overlap) + if self.show_overlap < 0 then + fb.bb:invertRect(0,0, width, dest_y - self.show_overlap) + elseif self.show_overlap > 0 then + fb.bb:invertRect(0,dest_y + height - self.show_overlap, width, self.show_overlap) + end + self.show_overlap = 0 + -- render highlights to page if self.highlight[no] then self:toggleTextHighLight(self.highlight[no]) @@ -668,6 +677,7 @@ function UniReader:nextView() -- goto next view of current page self.offset_y = self.offset_y - G_height + self.pan_overlap_vertical + self.show_overlap = -self.pan_overlap_vertical -- top < 0 end else -- not in fit to content width pan mode, just do a page turn @@ -695,6 +705,7 @@ function UniReader:prevView() -- goto previous view of current page self.offset_y = self.offset_y + G_height - self.pan_overlap_vertical + self.show_overlap = self.pan_overlap_vertical -- bottom > 0 end else -- not in fit to content width pan mode, just do a page turn @@ -1183,8 +1194,8 @@ function UniReader:addAllCommands() x = unireader.shift_x / 5 y = unireader.shift_y / 5 elseif unireader.pan_by_page then - x = G_width; - y = G_height - unireader.pan_overlap_vertical; -- overlap for lines which didn't fit + x = G_width + y = G_height else x = unireader.shift_x y = unireader.shift_y @@ -1223,14 +1234,18 @@ function UniReader:addAllCommands() unireader.offset_x = unireader.min_offset_x end elseif keydef.keycode == KEY_FW_UP then - unireader.offset_y = unireader.offset_y + y + unireader.offset_y = unireader.offset_y + y - unireader.pan_overlap_vertical if unireader.offset_y > 0 then unireader.offset_y = 0 + elseif unireader.pan_by_page then + unireader.show_overlap = unireader.pan_overlap_vertical -- bottom end elseif keydef.keycode == KEY_FW_DOWN then - unireader.offset_y = unireader.offset_y - y + unireader.offset_y = unireader.offset_y - y + unireader.pan_overlap_vertical if unireader.offset_y < unireader.min_offset_y then unireader.offset_y = unireader.min_offset_y + elseif unireader.pan_by_page then + unireader.show_overlap = -unireader.pan_overlap_vertical -- top end elseif keydef.keycode == KEY_FW_PRESS then if keydef.modifier==MOD_SHIFT then From 9dd9df03566c9c6ce42c5c0f45fc88bd7b00d3b7 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 9 Apr 2012 15:52:36 +0200 Subject: [PATCH 120/183] added dimRect and use it dimRect might be useful if we want to display status messages over content (as opposed to erasing background) --- blitbuffer.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++ crereader.lua | 4 ++-- unireader.lua | 4 ++-- 3 files changed, 66 insertions(+), 4 deletions(-) diff --git a/blitbuffer.c b/blitbuffer.c index 2294b8d19..5d0a7b8b3 100644 --- a/blitbuffer.c +++ b/blitbuffer.c @@ -423,6 +423,67 @@ static int invertRect(lua_State *L) { 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 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++) { + 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 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++) { + 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} @@ -436,6 +497,7 @@ static const struct luaL_Reg blitbuffer_meth[] = { {"blitFullFrom", blitFullToBuffer}, {"paintRect", paintRect}, {"invertRect", invertRect}, + {"dimRect", dimRect}, {"free", freeBlitBuffer}, {"__gc", freeBlitBuffer}, {NULL, NULL} diff --git a/crereader.lua b/crereader.lua index af3ca86e9..242833cc8 100644 --- a/crereader.lua +++ b/crereader.lua @@ -104,9 +104,9 @@ function CREReader:goto(pos, pos_type) print("## self.show_overlap "..self.show_overlap) if self.show_overlap < 0 then - fb.bb:invertRect(0,0, width, -self.show_overlap) + fb.bb:dimRect(0,0, width, -self.show_overlap) elseif self.show_overlap > 0 then - fb.bb:invertRect(0,height - self.show_overlap, width, self.show_overlap) + fb.bb:dimRect(0,height - self.show_overlap, width, self.show_overlap) end self.show_overlap = 0 diff --git a/unireader.lua b/unireader.lua index e845bd644..5f4c99a97 100644 --- a/unireader.lua +++ b/unireader.lua @@ -555,9 +555,9 @@ function UniReader:show(no) print("## self.show_overlap "..self.show_overlap) if self.show_overlap < 0 then - fb.bb:invertRect(0,0, width, dest_y - self.show_overlap) + fb.bb:dimRect(0,0, width, dest_y - self.show_overlap) elseif self.show_overlap > 0 then - fb.bb:invertRect(0,dest_y + height - self.show_overlap, width, self.show_overlap) + fb.bb:dimRect(0,dest_y + height - self.show_overlap, width, self.show_overlap) end self.show_overlap = 0 From 37f1cf4ef970fb3bffa190aa6ca4d9e4b8194687 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 9 Apr 2012 15:56:15 +0200 Subject: [PATCH 121/183] added test file for ZOOM_FIT_TO_CONTENT_WIDTH_PAN --- test/tall.pdf | Bin 0 -> 388039 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/tall.pdf diff --git a/test/tall.pdf b/test/tall.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c58ebc3d874322d2af51c0a2fc25b2d02b9e383d GIT binary patch literal 388039 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

sIxF0X& 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%0D`~&ZJUGJavwG%S zP{FeDt8n)-1){%_rFIHW?nilEh=-a{*R;jZHCiZD&w{ z7aWyZEFz#3saU^~gS3;j_SRuO?I$auZZ_8P1-ebOyut1usXqMb8TI`?J9$~@Ok3X| zC#I}HfI zAbo5Nuz3LG1Fkr$P{2IJ(>S3X^fi54f{(F}^9NQNJ8AFZ#bQk%19S;a zcClutu{7~PJ^vE;xxkrkcSHbp4MZMb&1iiYjQFcI8T9C={YAjo@^x8IWQ;W8r$LyU zUM&eCmoqem&&0rwbEYU%9tnsg!UzkfHrQ_I+Wt^?Vb65UY6xzCjvU~Joyp@waGv0I zVyrDB6WXe_S(=lJ4DiNB0pM{>QzpqBw4ATGf?;sLv8WRO1GTMznceHgfCXTPo68`X ztNbZP0J+>XL;&exT9YA~iG#yg6v)e+>|D|IPojVZ;ZYx9&%xR?#DsE=JHh*zE)y#| zl(Q0dVGutob7>CIjH1+jMp{y^H-Y2?57pA=5z&m!qDf{D|6daIAFaYEq|X0p$dAB5 zHig!}pqvrY57;M|`+<6>fI&8uYHb4fpP}v-u!Aj)0o|JkGyJfnoLJD~tlM6jVD_%Nzhhn^7!+*v0! zHd9O?Tw%rUS(1^HdsCdr5$f$7sj!mEn|n^Ma_tLOhV9>ZZu z5f5H+RN)J72+;MM7K+{LAix#muq?UAaa?e;Q4~cOYd|a9U%EYt^Tv7+?_*?KQ;YuR zP^I>RUB$YFM7CjFBG*zSO-_UczhHh}1pmN-y{!>pt* z&LS&=!|8MYno!l!)JjoBTDxhNBsd&Gc$CTPLwGc4>_f75TKi>KsrO(;pp$$1MR;F< zFpfms^mnNf2cT&ueY^ZaBsdL%bJcihy9XM1Au+niA5hiPj@nVxUR=L1j5$$UTpZIr zsn!r6F@p!ub%|4gX)b~X7oG?|3mSjgtkNICI<*e6L&us?k*K>S9y=^|R`ZINT66}e z3`qEr+|B8?7GC<^7pWU)c?qcCwGdbF#!VUw#V3crs%iWW0Np?$zu4CLSdR-zjKaf| zD+&Y~N|ZmcZYi!@4~WX=9I&pf0j*AjQjEviw7jaWZ4zP_L1A6ndPaQ0q0@IX0)% z_(h4s`#Qdnu%F%wX#n0!*ARRhsYIYab3cONY$42=uXv_>Ch?I+1zcQ-c-(lVy0%Hk z#pV=^HC`cJ#7BZ#?8cQ0Wk-|YUsK}9pF^SBv_(6?0lK#WL#hC}w>-gB0NvaCcGr0_ z!sX-l#7P{`3}`W|)*eX*_Ix|@5-!65oytizD-1jt9%V$ak4noj#!jN+E?uJxpqp*3 z907D6j~GBV-oT|~0NtB-1vLP=4VYC%WrGHGu+l1k?(GRq0nojB+@A%|{b7#QClNCT zUO|JL@ovh<7ybhHz6ya?89+B6ZQa+WZVr{St^(-ZBlfxh zbVoY?x(|v0bO-+(pxaQFyAd2_xhr5p0NwDZRz5+{TN!%-pc`YAb4>ujbZ>?*TD1jTjjUw2YgOFWtZi|i zA3tX>hK>A=tItK*BK^Shy*(n>K<9c0C;@bD0ivtu0|nd_B992c(?fC}fy)MgH*bL9 z)|W-oC~46&0Nul)3BcX*vS=Fk?_tpdNN+nVnhh?`w?(s=JLowc$&0o@j@}AhNp#?$ ztVxxqhL7{KXkuXfWzkHE-^BH!AHRv)OHYd?KD%BPO@kmlESlEw!=j0HeOWXO_xQAE zf`QLz(cIg|=DXGNFLj7oj@4<=3_{$+gQCm$=N0#9(cC*vDlrhn-w%tX&03zBn6&+} zXkzH)^@O0F{4x&0^Rj4?4z@42E*8pJq zzN1x=65!6|D}FNx4(@yq41u7i6qjyfXe&`Qz@3X~%TohKv#0T^MTSaZJGVFy0^8Y1 zC&2OhQjRY(SwskTMOB!q13YiY#`8u7l7|mc1Z{;dIl{BI1LAo{j1zBUM6NzL7KDU! z_8^rYk*&h&uukr@#Kn!_Y;4re%NIERl)VI6b$L{O5= z;UiDAPol73j!E3%U5ZKEUm+R!v!p!#9G7bJOlh7rg=MO=X-;7o#Afg*EW_P6HiO>u z3G{YgbeAr<5ZrP?vRJ?QCbR>i8?5^(u+{!4uSs`8B4U>>o5Jy)qsSJ2c2wCCAvds-(H#_lUw79&1&-ZIyj37UhH1kfxsX6H5@AZ$+9BCqV@znp+=EH^`79@{t5@j_Kzffw)evkwln2V z7~MOnK{!gN#wwXeUgR5sTk#F@^U7`n!`02NDd9A=s3#+gZl@Gsbi?0fqEHy!UC~S> zm~NFx4O3ni-Cf4Z;uzhVwv!T&U~tMGl*WqbY}+)ku51a6?xyIPB8+a#frVcRqZ=-I zs4*olx=Fd%ab6MPN5bgFOK+YxhSA*=OImX2FJ2CdZ%IsC=XZ&~t%#@+7~O_~^W+m< z=$J(q-B#zdv0uXI1_L{7T87c>6Xn`_Pwhk+Yfm-DF}hI$-4%3gug)7j7sDM9&O&O! z0$a4S5u96j{_Bf5;{>0qr1&e-yEa6&9IOVzkNi~qCp-8K<`Ay-p zU)w7%x`PrDKepR}(LJg=3x(0`1jpzeLV1_T5AAxG{D8Z<-X=eTVoj4D%75-dZuT9P zUEjh-RkI?{sB)P6tQ79cL^D_~lV4H- zquZbg4wE0NFo(&{KKe5Gamw7}7oB#KAB)JICO=HrJWPIulzf={&>k<7pRM-Ok5^|dSl6k`D?jEod z7~NeV;coJ?LeXjR%cf&=2Z0#fsIGpP{Lp^hCO<+Uq{+|r`C;<2E%h?_*~m|mpUIZP z04HM2M7wvt zOkiLagm7TL(tQmlc}9t*%YcHX{Fc^t#0d z&=oEU;j1stDGO1O{27NVL~7kGDGR5o?I8=Jh-t{e>83FW!wI`#NnwaGrS+7BNHZKg zWg&`7Ds0qbVYE{g21&9olHe!{=ZIUPgyHlAB?-fcy!mzr!>J;JDGXD?Hyrj7lHeJ&DXzD3;$x319ypt>(!h=j%h=7;=xGc0bq1Ge|**;`p z5GV^#WZ3VDo6}Pj_y|#WaZRk-qindf=Mlw)ah)X*$p{KIf1>%;mMokZry^MxLQ1mG zgxF45_?CObplQ$eN}4_?@^{Srl{DR~`1|1knfskI4Y25U(zGE?A4$`Ij6Rd58;VT6 zlcqsBlcw9#UP;qvzmulh(_cx`Xup!CF}QcqbW*|z^7L2Iw7v99nzrx0lBPj9lct*$ z$js0Iv|mZnhOd1lO@o7H(lk)M-$~P;oJrFy%Js?#k_2~xB!x~8d&QA7Z6A3hP1{7C zNz;MH?(6U=@1*JWv{%wJxb;e!2H~AFO_u+J4zy=hb95$6V^YqfX&c%r3Wc!d<#^&glcsHvzmleneMi!??UN&E+IG@c(sWxr%3iQ; zPEsB_9sf0o`)3*?=Tfa)Gc;P0B~%~3K`yU7kFx*8+HGWl8nD>E#=6|Ryi`l}0>xbLu9em)6AJ4n z!b8(RjMbm0@lxj%%RvgvJ(yW6uP&KgXv~V~C~i{GO%ihzvmDLOEHrvGd`?1>Jb^UdUcVp4L*L#33L<1?sIfU>Wm#t&!ZE$X|q0G z!AD8K6^$=g?XZa8Bhp%B5kWU(t*}cH^72c@9$_HVM$#gJ8LG$O23~mRCWPJ!7lb6n zhcG=h5F!HHrh>F+I{ey1+*rzqO25=vUr7oE)BXxNOiK8r)_P?r9ItV|)IlJ+dEQ3N zFSQ^G-@e7QqI@+r0+RdFCX43bgu?UDt#nHTDI5Xom!oPef*HFqx*p{{$uYH}P<1$_ zmKUX$V`|L^j5k$2+U}YPLiiTfh-6oBY<$(3$shfqynKthy`pjdCVc%dV?ArCa@6sw zBjq!Q>7nu;ri*H4CX9Sk@vEcKjDe0H<+lG4iv5U-1nRh#Owmf?=rn5syPvFES2Q{S1D8&&Z$GEO{I% zUQ<`s_B$y7IQEnGRt8`8TZohj&dhd>ZiESlzv#?n^+?qGN}!iTKZI}Q z{gkASk>pvhaZsMe1s20@@d~t~4l?z}{Y21_>5tnhNGlRG3Ax~&$brRR^A%)9ek8OA zb|WKE)zO3H?@6(VDqU9g09Oi1f+@9kNtS~WKHnrcz*38#A0jNJRwRfeOAb$;uOYk7 z!{-ZvD$LU73qbkdwe|79y=6M4&ll~!Up`-u?w&qh1GV=fwx3NQ<~bpTdOx`KUcX6) z7>r;2A~-A=3qj5%=GzUWL`c;uwdm@QB9#<7B9Nws7i*8a{7G1P^MS}Wre7-sP0$=)C9=2ra3|Bk_Fjm1S5NZ!syn)TuE?2w@U_)--M;d^k zy+0v;qZu392_}6B{JvfK@&jHPd^@7JqkNe3wS}6}HpURb7k#TCo-d+onr$Kod)+p#_LY?A9FnSpMxKnLS1c*=iW$DG zTcP%2?OPfKykfwAl{ygEtANN=Eb=oA|MeN+^CF_I2S^M1cX*&0Su{qJ9X9>JPkGb_lNz8 z;<`sz5e7EaJOvBf!G<~zk2KAkH0yb|2)Siv348?8tfR27!@dV0VBdox*mu05>AW!H z?nJ-S5sO(|=@RP!W$DP*L)lx2jXhalF`iJ!l>5ZMPghkP_I(AyUlELX)F(5JzZWDI z=@WUV4*TwKxeoiDs2GHOf1)sjVBaxKXQ2)d?!!}LqkEMy#Q4JkVo#`6r2EBem7i`g z<6vjW5tCR}AzEoql^FNT%{gG-pTsj0?0aG&8tgmpCZ)H7Vb6@AN%uhB^mK@9Of6tH z4|rs>^+MskKRxlS?19h75+V6A!U&9H`$bc&0pY$sHNvP2_dVKy`~HMX2vzg(Rs5S0 zDr>yxiz1t-8F~2!S6XB}J?_Ua_LWuD)T+ypC?Hz%5}n-}7N}wC}j!F`hpt ziswh$mWJZ_``Y?@a(pSOMe+RTs>Qf7SRZ3M@tnwxbx_b|Ko-OK!9XUYdptiVhLEFp zewzXaIK}h#MZF~B`9(S7`SVAN=eL)dFP8EAJs>*6thsbU@OXZkxUwT!A7eAkCF1!p zcsuXniAOGA#`A+))$z{_@fh9?y@_-^UGLIV3E@`N49b_bQsduNqfNWiLm;`zr*)4}qyqdcb@e)dw}f($=!ZF)NIKHFjV35t((ATR%21yH2(q>Z2b zri9^#j#e&jv;6Fp$tcigw%g3Z^aE_kTGRA{>K-09YIco1U$6oZ)iu>HgeknQJdjZkcR_0+WejQgD{^7q52{`4~hed4f9he@#%9$qJ0ZE0!7t7God06 zDyj~`SyX+Lk31bYp`+Yii524(HlnYAfg0FKi-OGpZIyW9wi)+PR_gN?v>m4k15x{Q z@8lzd6U$fddK2I9+f8;tXkyV#Esz*UsDLrlc%nw2DTHA1%B5g(lTt9bO(FO_)qiXy ztrINo?@tg2(uIDWSb1o_vGQyr+hGt|v7sDbc{Y$YRvx;3V&$QIVC8Wwy}|NqEV5Bs zKVmHEXK7zpd7?1D7(iY53IciMM?BOn`(Xip*-e>Ab0n@>bW%Wqm0=q_9Nx*O)nJ?-Vi(ef__y1jk1#b%DTtBapp{sZ?|DiI7C?8`5B#=yD$<_V@Gia_~6s!Ki zk6SLp_zyxssPE=y_(TN(R}}b(u%<}?L7GkeMua=hcpFT#|94GYKW_)KnQ}Oa0ApF> z-w_8qg+X;Yk`1hS9R}>1KsLZ|t18~qqa_%dY~r>uCsTfJg4j$e^K+re<8D4&kvAc1 z5VwD`=nkfE3Z?h<9X)ksi-3`tv-T!@&2v+Oy&b&NVE{K5iD_4pE8i+Xbz=ZjVJq77 z8Q*-Eh3swPHcXK5z$9ADO|%-=(6QQY2dhD5Oeeo?_(`U2?>C`pqUlIAK_E~K@cnG9 z5x;uMBypSTyF4 zY}ntG%(Ilgo0d`VvRcI^N}{P?<=aKo6m+EjU02$hz%)}4A-8a8E?dk{2ycc~Aq@{w z`X(&RRFuN~NQ(`EW75FF%zeabx9zLP9(cG7P0HPlNCW;MM*1cm%@i=l%~Jyid{&J4 z_HzZ}huKMlNA-zs1Ecvt-jVUrp=gly;r&NQWh`x)^uCrpgzl;_p^R4yf-im~DLntt zR;VJ|mwf~0*t%&!zZs|K$ac4fDO%N4&Je=99>rAN3_*Ng`)OCSKSNqMg?QkxTGBTo z6d&cr-;en9OxDo28J>83+GY^D8J~DGsj~zjwmP97ezg}fN&d}W_-!%>GF=1j?_nF3 z>ql{>Hx1X1gE-xOp}+ud&Z+GHmCEoLmu?#?0+|yw#WLi4*l&jNn=U}4T2wATE?FdF zbWQ*yU`#Iq65rF$Jh~-VwA-yQLGU3j0tEkR%Mio%WkLsL7#Ny5&yUX*Sr5W z#Cd`Rg{c0i^WrlA*qE$D(Cs%#v%HQVY!!6AX@A+$)Na?ex35RDzFa@M&;)eT`Vvhy zz68Msw~3bc)xL6x#4mmk-0Fg&l8YwY)5OefigyFtt?%`ES^*LqO;-knyKy(I@-{n#oZ zCc=Igjw|(JKfKd~`myQu5A|bDIu)gVL{kdL(S}NaUrVKc2C}VU1D6O@5I`52P@xI} zoW+(Z#-`aIG>|RT)HM)rh0J5;%?VyWf;R&;!Ym?WK-bhgFLb3dt63s4FDM->^EdR7m!+{M}eSuxGYod2NEF zI|0>x%M5*Fk67B&N48?=Tp#i3v(iVR>H0_zsE=$pprwz%9xKIF`p8z)gcK6MR!X8n zw`Q;r>?U){%r@S~Z~Mt##t`gxToR%x26pmZC>Z%PRN>nC3nc&kC7!H*QF zEQaF>Z{6fiuw@Gj675t0Kp{V=1C+9KeG`9iTQ)5!@SS0Wv4t)*pj4GYzJ(B7RsQG> z5b3H+#al4bRkbf}npf4nD5T6RmXFX#o3Ee|H$M_6xX}%>17Fcm7au;9lrKKme1gny z`IPLE{7QaOz6HB!?*5)?TS+jLtr*$gpCHg6h+a~0ro9kt9&m0_1m#|nn&~Ju4&TD; zwL%{6C&u-#0C7Aa$C15&D`~$zfpKIPVi@XTv}1Nixp-^>vR5yl@iyM?*4^v?O~~0n z9eg6f^8G%}ds}-O;hhBkh)pl}+9@Au>Ib*%TusyM*jZ3$v zJ2)=gqWR%1-J-cd3*F*6+M!zk#QsJ3yJb8ba+b9v*s! zZQN8mE_Jn}>Vf7wbProUB~=Y^<5WK2XpJlN;+ENO?E{G^seK%qLCVK_yYSjm`W-@s zfm=yoNEF(Z5E!o!tmf@8RiKRi=+uBTG_cN+heDI}I*$!hW!&czlOeCSBDX-`YRIn) z=50nlgq!I7jJC&@h(d9RsHl7eqo(pB9$1zAVB}TyQ^8v~6n8`AU`Y`-j#oIp;v?kf zmHh~otDj$!!iiFX+3nF=PN|#GTLo1o4Ml;`TWIs|!S$|^zXn9I*tcd{2$eyM1&5Tk zIZ@-FpnZ4!Li02Q)gX!Tp};f_3c|N+77Dw8@GZ2dl6v@-C`1KtiQ+$E_?EpCpxc|_ zTWI6zP`6uvK-p$aQ23Tj-NWyiCfMkt@HS`eY3q1`wJ+@>V{qmWj|2k4w~T`i%@7FR z@|iNRzRsER@U5go_?Eq-_7v>+PUjiLWw|BSJbnw5Tc<@aak_+lt7FXump&UU+o6T2+~xXN%dqC3w|3SH#|g);Xy zg-SP_=LLlrhwO%?v#Ane8uLn5O6yJUd2w9Io38QFU6l;)d2wJxe+0EMZ%GRrrXyFO z#1_yS8!0f6p*_tJc+-83!MUX4^*Jf-KG%dFH179Bp2^q?i=t_*))s+J3Rp6Mu;IgYhF++r&CIoRh4jiiUM+z6b;G;H6-@As0l zgJPuXg60G(amqUuyyy7rYCO=fWzgo(-FuFX?kNuxyyrGacY2&`XnUNxSXan$JVAJc zTh4P^<#HJKDL7du>ZbD??OO;daGu-7k>=^n^D-v`IBqY!l@u?%Rptoq7)o!UO)~?p zw5T#!<$RIEm^}mnYHy)!wNia7Q`vAiS`-HzLAi{;-Nf2k1@!{U^K;yLQMdEf%@?cK zYeL?vy`}ccFcFVf?X7$RYj5Q@;#wNLQ0=Ykr^G^x#7h|DSpA6}FMTn~i*F@6S$r$m z&Ei`*AzpkdDN%eYe?(^hUrNb>>RUynK<$1-5se&{M`Gf!=I4@q2!+X@@I!7Oj-eh8 zlz()EL+vf_whJ>zwYLfoT#E$peyrW(cIS58VYSr+1;uJ_*$e7<0M0w^A36Zhi#759 zoGN|hB4G-t?l=K4)TbVgbS8=1ti7c<;)zJ1F@-F3v-lRdy>)khruemuY-@Rq#M{$% zK$+S%pM8iXH?-Z(P})((V&=(!LY#}6**gXKE;ru@Cn?%`AG3GLQ^LN?2UHvQR`yPL zJn}@@J0Q@{$m|`oi>!M#uxR(_)h&dXGyqJ+Tm1(m&i{DP7lp$!7bqxQrZ1Za$7Pih zv>c9W2^tKzc2{05V+lR;np>;Qm~&08a)0I%R)zCs_72)^W@Yw{&C}pukiElceH6~(Dfdea>IW!S!Jbud}1-J6~z3-Pu6>4PrxlA~^NDTuaNldF;dYY}gDT{X|ZC zxnD#%oGr)~>-9e*Q)BR#mAcvKVWhtZ0DK(&^c;fj-H}22xZAE$e`vRa16TlQsrE zr%4?YE17>hF@?nlRm$dsJ_*;QK(at{=~6b0ld>ARR8Dm0Qnf6FU%Hj(q|hc*i-#zl zl&08ZSc%YPMysquwY0Rx5b60tnc6bg$<@m8tx$Dp89MXAN(7R{@5dtqJ(Q6MP2Y4v zH!crT+GHfcQ11Gqk*F3`I1M9FQrt+SAN{zI$X@yrCL;T)d+3P&jc%0{CL(lu&Ai*$ z$nJ4Sc<|zL)wqsFDv?Q~L{yZC+^3=fN?D;qU?ypwaD>_*1VU|4giu?Q)FR<(-)P5K zXp^{iT8Pk19`#y*MU_W$jTY7li3c)-#VkZ1xN(EJB>7D_z)AGgMibHZdgK~?!l_3a zkx?3zb=Zi|HoO4Yh|n)L-_S+`O6xssL>SC-_~&eJr@IadltEMeKvP7QY(%!k74btG zk#TTRWEq+^w#lYd4mKid*PqiwRI6ggBb+8FUJ$QVuXvP{#?Fg^Hj`rtOXPvz`3pjw zx02k|D7X^Us{H3-Me?F~Z+Dy~Ddb>yeScF_%`SgL!gWwWlCAw`?L-* zT2-6drDXH?oDic;t1_Ob5zkIJieip0F=`y|DMn+tNsN9iNG{SE77mFTQ0|&y6yJSj zicw@eOGT3y#VnP2h*4Bi?}wCZcLYgFjv_jxrEvglE1Dc~w63scOO7%=CFLkKikTfH zN2>}~oPxBM5cyJKdx`g&65C5y)=i1+B}ONOkuK;%iv|2=_Xv&0+F^R97Tc8j=1;WE zB9x)UwxwT#o~V@qq*QrM9d zNNa8dlvq`W+^277(~HY;+iIUqw|s=w+Dpu z`D;s0U*)tv)>$HYUdO;JDz_~wW}P+6O)zq1JICgfs$UcqBf3>?JHHYqeDp&)fchCK z0ta#^5ksYg@`)j*hreMc)-Apa$A@Y+T75$!j3)*kN0p=J{)-*7Wy``9oqiNqMzJx|5%tIEsr1&lny&bv+6s^lu3M58=MtztzQZeD0{KrU5V__Lw#r>Xwsgxf&y;1p($24 zzu~KO=<*CfjKk&eeC`^P>=_qjILDoWWJ-I*qO-6(wzgE%3eji0-#jW8j96yy>(i!1 zkOiD=7s=;SwZefNGME#k>nvQ(lQPqE#fNoEpck7IPGLW&dZ8wPB0p}64V*hJ)~4Y` zdnqbADMIg`crG+xY-=~#=u0{F6|u>Tav3w+!U1vk zX|j@0QNpn?xRL@GsT5_}OntTqHgOoFiAL~I`6=G|_1xd(ZI7RAcyh7@z zegax=4uh#^(ay0s zMe3-%;U~4xJ-^DRWG|#ks+V!FOw;{@kc# z>Sz#RBtg+w;}y<}Os#cpSvL-KlpQ@7{+<#8NW4{SHcT>h93Fl}v$Gw3MLR!Fzao~} zKK+VJB}>1eOfeLx22J95!A&X*HkEzWpSKRVQ4xwY- zGtKo&_xK_y)92B4bj%(n|79lQ}WG2d^sUDXB|H64S**0ip6pA%v@b*;>FV1LAVU!{l=mM2yZ2Z4=8Ix)uM6^%ge@saBI*WQiWoJUu76nBqv zC~E)7!NwCkG9Rj7;j!FfV$7tSBpYt?OMk3uFt2J)&YNYi4_f%6+zT zlCmtr)3?8Ybr34VxPL^I+x}!Xa5WI9#LnAC3?yzNoF!KM_D4ER1-PGR)?B*r5=oRu zzQoW~!SAQ=LQ>ZJ`QA$=B&(_%Mpd7r#Kv^Yx&NRZ2&!hKbz8U|Rb;ur38mR>R#vy`8pSd{+4ed= zNy)(uoL|>((kW--XTK?4?gtdbg|XldI{YjJ%}oxVWsJW@JiJKUvNlYsQIrK-gFrZT zK)aO(=zC<#*?McYZ})mWl7-9dNA$Ml0$}Z*?l&o>y!j0y4mCqRA|Ct%MgSIan9TCy5Ja2;( za~wZQ%KguiPLYPxy7YjcDeHNhw(!<9kJrQ$FSiZZVe3slYrSo}xv;37EMCWT=Y(9X z`pq}taDTM1A}*PgF_S_tug}jMh3P*i)2t4V17n8?(?|!U927T^BQyk`;w|tt)aT)d z?6=!yL1Kz5!plBf8px7Q;~_f2?AF)^K!3`8fQ|`q7#{&=M~U014s7_z8c(r~oXhT~ zEg`1VejV8H2;9NP8@3^}#WxW9VR!rJg`EV@yVcK-^BPH-sxVfo)GW1=z#zIL z=XDfd?sT5u+5?7LKv`jh3bbjazk^*n%=D#O%8~s@4weZOGG5An3HxrZr&@)c8Q_uB zD$J4zJlCrRp`KE$GEB4M?~Sf&yb|+Z2b2PJ(={dygU!q5=uo-<0;qUiWFOn|^&EJ!wSh7`#04v4JiK+= zC(Fk!J95*1Q1Yw}lFn*_rOHx#XnWlZ9@eN1THSz+2TUJ44r^4% zoOv|kCo^o|d;&E$M;_Mm$&6DS)+kmx@~oa-ElX#0wBxJ}N)A>D=)Xv(jErI z_^RWqHYBQ+XEor!l%uD^8YA9M9@fYi%S?{Tm;{yW5K_oY8yR8LW>di@9oDl&%_^PN z$WQG3IIUv|=(PS?BwRT275-zVHM|x()x6JQg3soCHU}jb6~8))Xaz9>2;+xgtxbvp zBIK_QK!ndel@{rM2tmnLEn!#wrev@3MbY)3I3PlU^>^{uL5>32U8ZRhC9C+gU0DvG zz?$ZH5N&;3H;}lrUdPbGLO8hyqHrk@5!xe6)!aFcSBGUnM|-AB8%+36`*gwQBTO2} zS1?2@KjI-`*$rVLct^t~qoEvD8x1y}C}H4)_55I0*oK0mOtDFhGX>{+x^1yO>y=OO z_a`Y#R6zSor$GCiPGN(Qoek$M+DATx4dtCqfzF@l6gHSlr?@zjGoJzk=BB6@F&6bQ z+A&L{$eRW&Fkm^=0mCi^~&AE>{&`9iw{trY|FHSOL4n&lhPjy#yk zvYUh8v8G93tf{>sLrv`)^^DlJN!%?%O)+I$Vv-WOyoyG0-1Hgx6K=Oxs3{1nW8kr- z#_vk&B`2|m5A8N?zZWDIX-g+6PbE-P{|HWYwA)SJwP~NbMs~F1)d5)cR2D2rF9+a!x;PG+oWLn&PuGMJgw zP+(^T;ol_02^hagzyL!;SmrFHu?i4GA{29807}8`${-BG8KD*f@&Dl-FmU;_0U7)O zp(9Fqu>iuS367b&_QMx}7<=>cI6>s%@6$qaFZ&vP&X`j3Mcde+O2a7!=reqt<~#G$ z0#M>H?Z@2X<_EnjMy}9v3lO7ey9P~rIf7e0|7T9(7#1Q9!snf1ShNmWHyrz)u0vr_ zn?qNGj_uEe2Nr`-0V**I*$*RDMArq(u*YG#~jsg8(~sv7e)F`?h6+ z*VC)30=W3RInIiCO2PIjC)sDS4KEK7m_F0+z2fgI{dAd=_ah9)QaMh?t44(0U92|{ z6@yr<=k3LI$`C>PFl*L3Cgs=~;$Z0n$0{^Lp~0^`jw40wQEjpwb;+DC znx*tu^Rv2eeFN{GO{)o_S1aFsSHq~WjgL0$3twBnKu5$c6u$mU&R_J%%n2gkzV~o4 z4LQgvouj3>du$bBZacK~!Eh?D%O)?I@jMBOkXHWlZE)EBc8umCv+UzY@%+rOcWu+A z6jSXcCTzL=dWh=*YK|SNpE{ZJF4;XWopS8{x{K`Oj_S@$z5{KOTHj*PN@? z^()$#30yT-i{AhJvAQPF1~Tv9pW)q~6#CYC!j5PuAb}QPQNZbK)?1i(yyeT*+udKr z-vSzE=ew(i$@#~zj6b7M&lWX#@Az`XUXUH}cUCCYk1t?A;F6}-{}?C)BF^LU4!{^B>&u0tf?ixepkA(IMVijdDm z{GQcC$c465HjoU7W(>#)&6JP$d9`!LU%%`+cZpw%BBYh*n zu`LsyJRS`w9b^k%UuPMI=$kjA?<8C?IR81tr@uaVqwhQK*g5BG%!NyD1L)xN`$gFa z+i%6TPTGGERzP-C`hLGDF>E7P5!S^k@~3HL*u@+2@=@9S`1)h06Rk_|c7RfxbAZoa z1H~LXi&9?%Ts7y4nW$bU&JYQxV~^BlpwK_&K%Nc)csL z@qMbY-n&ITmA${B{U?U!4`}d<_u*iU--d&Ei?z;!x4aC0%_t`MvABU4(chmS@Q#TN z4+gg33i>Q5KfX*Jn0+~$Skw{~Q_HiAIWuU}h;q(MyR^?oyruiMQvy7u@=V%PoN%b{IC@sZn)gD?K>;2S?3TxHK5 zMQdc&|95>Z7}OyA#(NNWyThFD@XIs5_Ms!WgmT#I zXxodGzwK5qb~bgfy^_L$=gNv-07K*(Al4e&8)vvL; zT6mRx5x3Y~BnN~#!Mq1=8a58)APLYICYwmUV6~Xk_{gwAoe%d{;6XPkzqr1x6?cQE zN_eMmPz~ifrGwQbMpRklm>dVf>H@8(ZVb=Joa~3yGl7L?h6FHhj+cx;)n5(ER^Ya< zM*sP>4=G_^*oTzB0!}G`9`2OQ%(B=in=Pqnr*KA%+IYPeHIDDI>T&c=MUn56&gOsq zlLPc8i~;e2WKon&4koH<@*M}h$|ir!1LwxogeaTrlt*QgZ@+x0Y%&7=9_Ep#$j)5} z4Pu!mC~XE$5|qtHMUi2&f;fA;Z1N_en-c(>G8t6=aBdd=$dbt@a%`C_te9-rG+lWv z)nJCO10S!K9E8Qn0{`YOSS@+e+lD40Wr&7tO#!K}+8Ex5pvcW&)yvv9p?ypmJy9w7 zCUQhRK}Zby0xBinG7mh9`AKBj*c6FYDcL7S0yHNpDka}3z!U-rSy{UH#$&SGALILiwDI002FfgwtQb*Q!XC5~r2wH6s6H>El*K zvJ{r_UMdno&Dg6f_dE~G8O?fO}j}+UwOGPA+pI141Rz#9TiO$)xIJ}ngi>L>?Px%@tg%|f& zB9`M*Uk^NsmuIUXS&DRk#Q^s|HgPwVd#fRdc^sU7t05UPoi!xut@}k($37^kg$gQC*dc!hKq+n`;#Yt#V5ByL-g zsuG9-PA!y98wK}pF43iBpIA_wb}x-xw)BxGjcu^n`^Oc}TyCn3{k0%%kzxkgse2lw zrt*n4jj1Y*U6uwP#j%OxBu#B{j(C+&@lF+ON(~fC6ke2kVi2ied3EeEbRUZ9*x=zz zEz~y3N;TBl_CK@>npYcYxA)=T2j-^S{o1%?J|^d0|p^B zU{JIHQF%)Ccw(HjSfh@D29NG@?7%wQ=uW&DR(d-&AdBoAPi#%RZ$MO?S^M08#`2aM z&{v!G-+MJW1{QAtvHh&%P|59w&-RhquPzNewjXK|t#WKX)UGKbH&=ahV@;f(X*HDd ziDB6`x&7*pn2haLyX3#NA8J(9mD{hnxgMHN=SBAl z&8Jrj3<xn?a@_!zKxn_me&O~9={`1zD|Mfk=G1-q^RhYoHTVO!@DUDShIQ2HX9Mg&*K^g+ z28so@tmb%5C+t`y-E#pG~7g=cpHJvNs9W}ee)iaa2%X7m3$tz+`1u12FfZ-&XE<49wj_(C@byF9GVC< z9v`xCuzq%YB94dXgoB%~kAA%PAo2wIOse6wqI~6!bM`=0-y?%BO za-}&%1NAqE4e^PD1F=qA$xL?h*oW`gAaco2`iG}n1SQ4DSUkvn5tJ~XpIpBv9AEz; z7>BGhjPb+Z+r|uAwrWm+#1n+e9xiO%4$O8n+#8wbI_i~|oFd^RM&n0GnL5{n*))d4 z9rGox)H(rNM%w`{4}zgo;SKWo5a4naT>}E(67EXb zCi#;ZmPe~Sjc&g;B_hhZZhw4)wMT3{7`VuBOAQPTAX}M-wGrWj8kZ0*qa6sB2PF_L zCt)O409-mDp)34XzbV4EG|C+EjM}n5cRTH9R^A-L8B&b{6=u%xd>XpSjB6)>Wo*Z% z971iWnYuCfZXr2K0GCthpjWdgtSogH_omyngl`$`z_$#F@GViB%kQdi3F4!?iw3?W z*2AL^Dkzk*hQ?#~ma}Pj1eP}WcLnKWY2aH1MfjGOZXHpJ>%mn)H+N6pyix$c;&rI4 zh{t%Vt+TxpS=&CvO=Ez!1k0OS)2c>0p42jc7*x31x#4b?mA9}wof6oV_{rWsZhu6Z z`jfCNr<>&+RNFXeWzUwtEvK~JgtVFO%l<@3tn-PX`-?$aUWQLY6HZ`T&L>9#o2Bmq z`BUEegm4+f)?1z>BBT(r0z&OSDJ~1`H`h1eI7+fjtcnsElyU2+aeelq1O=CNYETdB z4a89*T%F@6iB82?LdxqfiJv8_7QMInR=3o~Qg@gWKS(Ie+Gq9P#vs8IK2b(hg{K|5b#MB*jxX z;zv#ZvPXYyP9S@D=$AGpWTIYXh{Ls*aFZfKFI{Ukw6#T3u))xOFuS!9C@&QSZk%O@)3_<_E!`sUMC0- z{TtHo*DZPK8h#K?5TmYH;#lu+k#J=GkrPa)&U&c$?C8Pp_mmKUBi-k$AeNNa$9rac z>lY;|h$V>?#LynHmzphP&$Or#x^)koIxHcEc5gCBpg0^@5@JQAlk$lc78yg} z+coYzj@IUB5NisSq(-!ZO~uic(J?4F-1|F{O{7!)#m|0I+`xj+iK^HPv#=nvqVa}n z78Z<0=d3I&NLCn>v!=+Pz*5Yh0jPKsw<82i9n6|^-~c}|1>JyQd1Uenpk71U)gfWm zf>;48A@;pSh*(hKA~;MFPRgTVh;S5g!W+(4xWyEyG7!~T43S)C6N?U(J3SY~{}?kv zF3>ItyHYVJBz|QO1`$=bOCAu}{=UeRQg$ z7f9bWZBx<^LpvN~LWa5P@xa4S6tsmU(ws5@{X#?sc zSf}Ivqsj}?L}m&EoMyW!Ecm26eH`3JOe%jt{jLQ<%x&PZ7K1YgQ`A+C5q0@)j}vMR zZE!0WV_6x3%XsHWFx{d_l6Bpp?$P$_3*PF$f9ep!GV5(V9$wjOCl6kO5Vh~q<$KUt zOWru(eB$*yq?a5nm(5n#K)A2hNI_U{d8|%sp?)~s4LE4}rAWv@io#Px!;nvl^>s~r ztq`1F67P~2ROZO?ocW|gIq)*Rt~E)CocSbGZQY?T1m?^e4vh>NEQt#*`y-2%Kz#1$ zE7U-Go>*L}6$wY_TE`RHDl^#%h1e}S^cLmJ*Lrg`;zjiieHURV+4h9_q?~vqax#38 zj_M74f^XQv`Dwr@2LRYxC6`U*{@ahTk2E@}J*-RE;kJ5BY?4JIz-5Su*DXNP3 z04R41b!WsK$03C}Bt6+qL?)mA8Yocb3URm}9w?bG57-Siwh*f@C{NU8hBJ80|Drg1 zul(7;@fl_bVikhoF)lWfQ|@mDAOU@%xUHBcOMS!$l`@5Qa-}MX_8Cw>P>i)>hhBs? zQ3Qd3hO`p-8Bf5n+)oB+8-j`)E|0)Fx|R41Ejb@)!hZ2rNTS9^ViQC+lm!qS^*Q&U zS?iLYH0K4ondR_3)r2m&Pfx|)pCB-3+`4^Jgxaz~{VNZlx*dfi zqQu}X11O6o*~dl0Y-mr1qt?geHl0zplID7SV!D@s*gQTC^)cEpJ*4n_!cgN4jOmXr zMBU`*0+Rx=i$W^QQ6%Jzk5lI``H-&&PQ^#a%PYI#yjCaQbHVAEp(bf}IwkjF>#rEo zgL3T7cu_)@I3;#xBe-R3&o<>G*7j_I$2|6D6N0Ai&nART-=A%L#TNk7gkDwOpWWlM zwLg29F10;-jlgT$p1tw`?M^nYWA^<4?albT_NIRzwr2Fex2E^eH=d6ox2BIXo)8~= zYbL9FZw529H-p*QoL@8T$U9V#Vs{2bOTQCIG`2>+-5jc|({_fnV@Qqonz8p<1|s^< zGK$HErt1}uL~R^xzCJN^8FmrjBXJSp?UbHE63vzGukejAV=wvMQ7?RRJ$YTQ{Tbtk z{TUz70#;oL>H21^dgw8UVcpnr$`~zVGsmR3l$`lS?)7JU9F@wobQ!$Bn{Bq z^F2+1wGVlNRn$7#xx}5qqSo%<_nOxDhOQ;Tmzj~$X*I2_lRs|l%TYv0Yq*GMZ+ zNJOFS_$@;kP;RYM-71uwx>-H1iv>Ec%+64%12kzO0Hi3SuBi#`IQ7f z$xE)IAdMr+Ge(kvs3RWgxCQqq-P+$Urxj*p0LavY*NDuPGrE&W=bZ;f#$fkCAZ3MyBi-&e+)L#HJ-@Y}TUj*eQeHIikkV zp0B8Jpbf1fYTVF=&Zu$V!#royc(oz}88wb}Iikk19XMm8oQ6CA)x4Y(K}Za(89CnH z6ptFOR_Y<6#tqBnh#Id)2EI=0@pWR4r7Yao3!AbePVAv9k>i1SM2#B~&^u}zq>)kM zHjhWtc+PZ!jKTMA1=|B<%qcm1_*P%hWl8ZWoB1op%?g48jU5ErAt*gIGU}tk+u9`g z1aR{qxds8*2S;X;4abn7dL%K*7!v)8Dw|1)36Gy8#eGLIKOsOtVc~`}h7bm=A||hc zGL&s)Kx4Cn*U)Tt(RQG*Ab3%019p0I?2eFYXLC8~IJ_wPC~94is!!(vYqvPSiBtYP zC^_cG!vWk`XXc$jm#w3Vh_V80X({e<3vFpN!R!caX&FrNrD_ta%}?w{Pz-Hpv3$Li z0+1#!mZF_#OFehP9g*+F zv&y855^c$cwgPPln$=oCVYZ_i-n7DHnA2))9%?7FC0JIA_;}Z&9cW7j#jJnf%yzUT zQ{H%#$ti(bmDa(M$CDxYLR+%_C?J|E+~#-CDC=lTTLGw!wuA!lw)pIXd6v$(KwEmA zNOC3G5+=pCcj3%GuZ%U#q1xaX2 zyQ!9n(3W<~dOloJ+YpQ0RccT;+tn)-MTyW@ju8Cx>yM%*BF!30ZKtm-(3ZBsO&o1$ zXW0=s?!E13OHN5_DcJB|ag`z+As+Fw-xPP1+G^P)U8UCFeP~qsmQV|g3M1yqGm(R- zoS`mV_O&C(>5s9QB@`0jWh)@TEdINPA3_!i6oQ+6?XflL3qWW~UrU6`<{c{Ym&hsO zb&dxmm(5o=&2_#i;%DNA$;zO3xJ>@)EQNZjBv!mCDGtJJ05RG(Nx63rtRSAM_!xx3 z%}S|;^~9>RsQp*D+`8&KvS6u!vPW|TD0`Al@cYleP(MeBBZVzuF%Hv+vNzUv$Ei?kh<)Y zht!F3%8~rpiAOV!kUB%>IYR2t?(dK~1FShh>Z}Ol5mJ}!K%aRcmuvdkTW@~NcN}B> zP>cAjI(*1A_OjB0Fg>h}NWvsXK3&L=TX#`mriV#v%i^vcKVT_b5wgluylrtJc1sPJf9>cAM z#dCS=9x*6pPOqVrjDHmVR|rIMD}Gfh9G2p%$U77!9(np%f$5{I8rLJMi1|=eVpHX1 z4oV!x@uS}q2Pqs~^<;%o2=wwQGrHjvVl3-Sg~ez+OVxJFr#*IhVB5$7nYwR*r}Z%L zqRsqU-amkl^8ajoNs`<;&+R-{Q7dprVkfP~5ikD1_5PnE0FQ?8<7mlv8j z%AyD6_;MYSeY&v(`bRFqzn`M8oIn`CPkIew@6AqK$OQ>vFgHzPc{I&Ipd)G-bAH9v z=V*?$J+Z9#qv}D7g%*4L%FXmJ^+TgkKku(pSW;-#ijo5U1WOM1M_iS_{Xp3QcT;r_ zJd(97IgFKc55SE2g1A*bk^FpZz37!*lD+I-=_OH~WZ1Gr;Vy~|?#)Jd7~G?sM->h3 zQ3V1k#s>Ew>CPPn_biifl!3k7D7sn(_pAg#8$ue~vl0Z&`Y0`N)HNQjggKm*cEjKv zZEPP{+}Yku?)3<4?$Ks8T$tRW`B1}Pm9?-9A8<3J$vp@+qbR_Plbx7b84cW5WO5H~ z+{9-LQ))19jk350%VB(z#XTqp?tsZXsu<`_5GMC(+vHv(nB3bvoYESFj_%=LMX3C; zKhoqLojenVOj7X9lD?Y|3|;>k+XF{ol;5b#p|alyOl88cgm* zp~*d8xkj1X^PAYwS->D;x_R@*wIKvQt>+J_x@`9Kmj`Bhd(7ClZIZ|8B~qyuIBf-5 ztzC&PFt|4-x?G;(L171j`%cLcRkz?oXmD?LGguJ}?m+?CAq?(WZbdnc6V+BAW@K=0 z+2dz7xVQUo9zfQTP4*E*DCGaTokatSdr%~t(&F9>C(C-#Zp-3c6j+!@OTyjEYqiBaCu#W!@GD&fu(&6)ST~;9;LQwXTHH&Hovd#&h`hI1 z+}ksxxKbX9P(>$xX@E~@iPUyM+0@-fhaJ`3r}@aFG>cju3-UhI-AL52D*Hm^4ow2a z_nd`zF;ST05T;G&yU$UusVr0yevNz6cW*&zQQzI#)OSs(vQuT^lEN!e>2iAQH^mxn z9v>it#{0_t5@jVdULoq1A`&A$D}o_-0!%s;Ub&PNTeVr*pAGUl=3&Ux8~gMb4qc|+ z_6%koq{92E1weiGbAe#Z+#k^i|9*PbdE6c8MX6QuWgvU)pR$eIFPf7@apke63}QC7 z_XB}m+m}d?X}uo|&)X=&`1e7HcDs6++k+s(KFsa?U?w-s?a{t3S=-fuu{{9QO)1W- zulUrW(BA2HKH9sbwjb?lQx?T9UCyQdBHXYJ>@QVcM3JT46k6J&JsA)Y_*BukV^FN% zI1kdGF+MEq!Ns)0Vrn|;gztv-e(*50rQMY3n^F0GQxp|bZ+X;@UBhW?-wAPw^sI)7 z^D&GSJ#FbqYf7!CXN5bb2uE{m=xzNx9eD=RP4&xBF-7u>`L&`5G8~lXDoh@*wnv*W zUkZ!qkv27T5xcY934If0yK2+gUIc~3^kaRxWQtI$LNEQI>|rteIviI5gS#m?0m><0IK@seYMdyhEy-Eb}f6KIOC zP>Lm*qbI8JohIHO90WRs39l>qKA?}^%cY0KJdS`sPLBD!Q1J-OSH3}{Nj~x4M!7Tf z@xOd4984R5*QWxR(X(_9XYj!IF+Z|oPglG0hjLpt#sxT1 zsykv|ky1tRMEb4msQ7;`*>`59d`3y11r*;oP{@jr((h(;aG>s81fKzoZ`>J*@d`>T zhA`3f3`M)|#}$w=0xm0hP(_>S7Q+SIjt<2EGn*b*S%o#fcx8#9tS2>rYB+f14bY&6 zC6oB)43Z6lSBQgv3VADdgS&#wth|#K7!v*~f;)HuaOsoqo*^yJLCnn}OekR_a6o~Z z5h49yJOvk77{yJ_zxd8;dh)Sg<^D zhM=l6Q_l1PFs#=SfK0#ES}wmE!cQ=!(T;1w1d3_auEIWxyUS!C;S`4HWNOGv1k+qP znFcy>@Wp7J;bu-onw|7_fbUHdu$Z(@WbZH5@ya023GC8X@FzBUK$pgp^x@8DVICfl zQ%>iFm~^&RLoEG-$NAmNb4LE=g;=rNK<~L&jI*BbHoHl0^VSaEalCwC-N!mu5?QAZ z16Ip4`3YC^iXW|Yx9Gw1GJKDu^K<$4$$3dH^DEkrQt~n{p8OMD=7kcY8Ib){OD^V( zkTNpVyv5tsl!w^_ojZQ(7eN4RQTL(otO~6qx_RMn4JXE8p5vqo<2}esG#>C^tSANf zc&n~Xap}FhSldPq02L(rGqaiM#gr9pov^jc^eVri-8e()>`za2YRF4w~{9 zhxV)92imGvHXO!ZD?iJ)0fue5iW}O?HQNZL{)^zfk#D@d3D_$r;kJCvWeGPrw769= zNT;^S>nChm*X_NM!R*Uh;imlXR*ATX?a#yxo=gyxU2@9Lmr3~OUAXz;+BuLY5}kL` zHN!x7<>Gxtz0T52`n*dw>GxT>37zBYq3`?jYH)ghL!T_*Cf2xsn^>m;ZsIHpxP8ne zi}XQy%ByFBYoz5d!2)WCQ=hWohKcs%Ool8-Z^p2wzNO*2B-TL4kp4d5AD%4V#^kJ7 z_1l=grJrZ_*|nuJi!;GCi?`|3^qsxxu$ne{%&TT%7ndqYqCU~2xj}#E$x%CPnF*JMWCfT z>H!^SKarOB+(?<8u#-O$pxV_j#ypd->mLHX&#E7k#`@{7klu>*flhSP zM$E!L*S-_8y3T0Z6D;t6FTAp54^?H=VOxK;P8^FuQ+3?bR(U5tjg%L94nSYOgo0_&&MVhI#k5m+T%*=J>}B!U2SufB2&kcw zXarOnE}C{Sd_YgU*^~Co!J=vB$r_rOoZQ60j%#-oP2)rjRRiL@xn!CMSuri^Nd=(V z&2yC73X6v&)1ng;&qRI{#@A%Yv_!>QCAD&7cO55O#K*He5yA?09tebKV+tb_f|L&1 zd9j4vpuqrGan-J!CCXqOGbK|`)K05kQSG$q2f>ToPl+~-q@*84TaTC&?S`>AmJtz@hL>P)BIWHc8zbW929@<{gce@!Lr1S)&?TYz05!k*$i#sfMio@jK#9!{ zMr@xDtLjIRnXj!M;OO74RWn)MrxAjdwuNZJO6d|m0f2K;L(TF# z!-l|Aa@+71S>mVlKh$yYK*y0XRpF5R&24(GXd;ToAV_mZf2B}@8tsZAEnzRQ}X`L8FM zRNe#A&=U%2y2MYLypgfQPs}7lU{?6?RBsi2PTP!C;pgDxTqS3iFiO););sE$2qJ*L zDd$5qIU&FsOR`TwfV=GtWC|d_^9#IUCj@wVYyf46p95*QQzd?wy);$7N8_jxKM9ld z+8(*!31o#IR%@Py0Pjo|pM(I9L=|nGga9{TFCSxR78-I0r#&|0iITdrR%JI>WtAy>J)m<0+H8m1XR{}sT$gX8e!A3S~#x8kc{rvhKU(r?=!vLBt%lIo}1)>%Q>ZiD^=FJk@? z-__g?-sap-M$6Pts<@B_tMWq^90LA?_~jn~N4E>1S{2@cU=jCgX^g);izkQ-U&T(z7&qS%Ij7*KrK6A zGR#}yyXPGy1r?`4J)gQ1#zO98WDJI8e1tBAodFcvrI2uRx)XMm*VLWR?b=_W@J^T? z*sg^B4POb}3tkD`%PRVIf7-(^((MUxg0~;@%1Vp5$tx7Nz1;{sBfb)j?;Bw4w(U-s zu?o=8RZ{DcKA0-!r14lah01SE$eoP&6UQaNM&9=zU$${B9Xd+xKB zd!ql8$~lP`tp$*9TwWvOY9H<6326RQIaA7hXq*v_a$qp2appWy#T=;qbg)}eITw>l z9OJT64i~06`!@x?3>NgAd0(nHmvc@#u~fNh(g(_lzUi&3P?s|jnwDc-P&VoY-kcwK zY}HKb{d{}}Qxzjt7ht?gmLI~2#FD=2w3fueQl_}1FJAgl%u9ZBIq2nu-<6{S)ive= zFM-5mtesYIFVQ~lUDFDO18^(##6W(RRIPAk&)riM6djbQYN_JYR7qbGT+-JRmh_EJ zw7P5Oj~pNNkyJYRW9 zV#np67_qNcP)zQVSy3^$lb+A8m@^W@9>@+99VrTnjs!vR0a0}%s!3(;)pNW_kMirpuNKj5Lo~Su;u5Kqslp=Io zfGUn`4@8)4ynis*Fba-DyB!KXocsj{JQf^@UgF+~f+NvJAcP8z#K7QQQ^AqoO{0Gt z<;5bTG(}Yw9Er99uUH~cFmDrSRHvZ2q%gScf+Iyj!IAg`Z?`!zU8q%WBsv9M8udn^ z+jBP%Duk#6#sUbh9~dwo50o28W-?d@)kcEbqhcPSiAd0lSvVc!p<*LNQL&MH<>(Tg zCq7Bts&N7-;}O1_y;2;NyDW=YjA^}gi55XMeY2@WYzU8tC^m8mscoL_<&%#us&Hbq29=;>`b|NKZPAa4bDW5cynpO0-rI(7_axfC#4e5P@4bMKCbyy*(uY?_sNqN}zR%UC2P-R+bEmBwI>6%D|{c z(=`b!PPR57cniv-q~P{Qiov@%Mm5FYy`9+*gORm)%uNhtcJ)T_pu?BIA@4jQr($Y*O&1oRkz)dy|5!5OqfXr>2JhvVjo~tN2sxz~ zjD*pYI1+P_PkHa5Q>Sn}{X|1(rv%~M{C7#h`$G1SL}3&$Sq|TdnsdBjQpkv4K^QL} zV2*-y2#6ZN)q1FKBkm}0S3J!d6!s6*_=1d z$iTcRNUYixg&QShp_!ku5JeTS$1DpIIOi-`n5Yr0WZ}(fvn31fPD>1gVREk_qe`;S z%%d#4b1jxd~n7T?9-iR%ENf=^+n(lZPcKR0EowXqQ z$7SqB>0RC6&qR^mjQh9TG!}&xQ51zC!gzm&G`R@{O|h4-iicw+Sd-y!{7Pa1N5Il> z5LvciEOo(z<7C1bJakvY;@n&K%JVCH>6a!*CCq<`+I z=oMOdo-KMMJztNaS5Ul=-?!(?9iVT`9n}7LDtbkA%QQ;WEn`MLKT&)#yec?Z?m2Us z^%y>90%awa)o?_f=0HG53rvh;GCw6uG!aHcM*s^S-tywB^5H{Anksr7?041tI_ay_ z6HFhO7B|c?f*4{Jn$Oyg zbX~*97nH@b0k7P2VpWyDyOI@9MQ$R(jZZ`ltnl2kK-l5!#mX2_G4VvVB7-5?o!+>- zQoFrZZM5HKh^5MwQWir|j1cShep8H-mqjBLo}EP`51Qjx{^lf*i&j#WF>Ku{EsJhu zF`iNN9s90lWZ}duH7O?MrlmTOsE>*PCs3Zt1=9TO@{P=CsJ5~iih{g`tQnYveVEyh zMFZ&vR87J~ae&l@tm)V)cOgpzR<6VAV=9#1uu}-xZVK`na>D79O59<fV46-i zARu@E>oT$^bfx6s!daa~8IB5%C01Utm4;Fln1oVZg%{7wD)zVPn(B$jG{;mT@8LQt z3xoUcylO1L7H8EkYcpPlEa{X=Z-O$jvW&^QJs2U7({dfo3uC>Is6&W=VZxh7 zJwzJfqvD`G#}X#z?R*kEaWnQeP4!uD5KvVz)n^I9$-8QN|6ESENkDeDUBbi^OX;Xe zh|5+90#T~uq+a_?NlX0;l%X@4-OXrGn(DKDXXC6QCXrm<84a>9Z3q)*6%);K6*0;Bd*!M4hZ27MWB3;lP~zBT*|pPp zsjIa$jabFnhJ9vwOLv8OhTsFHpnTj%BzlyFlT}HSi;j?$X6^s74&8kAd3{|<$=?zO zqF&bA#`Z5Ot;u=8KAGa5^IJQAsJD&UOiu&DvArq z_$`%1F+qi7HUdg55gdH7kW8P7b#x(_D8p4THBl5vzK|>=(`6ENAsP3p3dxvMEY)j8 zVX-+-d^TU&)>OmHEYPGq^_ShpT5Hh=pzO<8*( znZaUiP9`Zzl}lQ-*nU&0;DJdjco2aA^sb=RlQygi9z<=YTUM)hV#qzkdIn=ye6ixe z=)S(;38l%h;=yQ2pi;qu!Ax1!I~Yo=V7-H_&3Xr>)L^p!&+9j7-!gjbH$`sJ=v-8^ zd0F_r3FRe?mX1W#kLolys(w`Gg^H!&FF}-+>>bhwR6uI^W`7_+ABHWLJBZMW#p5sa zcuzme9egbiRFIlGti!+j9&iwZ>tM zd~8Ad7OcZS{;r>|htTI;s)hv<>Li@K8v&l(06{^Z@du?`z0uUQSw)9V3MQ(~38%+v zS=cC|OwOw4z(nR_k{$J$#4k}kK?lZnXUaitt^1UTfTBxCWmA1}u>?Xzy&l28D!cDP-*#fw zF5&9UZ9_RLS8u|IVsV7Y317zSb=?CR)alrSH+j73eP3x&}ov5KPhUI>O zwVsdhYVgQ|dN7ppQRCy}CyB@(_au6Fv*^rqhEJ&keGn9T4nbcO33|R{L7yKTvjlzq zF2frMN_nzu6a{^>kCN*#w$zg?=VSE8+z>H>fe$_KSz_iK9vcBl&+daTp)t>R>6)s=YP8-_^?`ld_aw+@dH^tYB zA`#!U9W*x+-}QQ&#zTD9$W&uaVB))8$&0|MG_AmX$RF2qPw<(u1BN0zNcF_OF(z^{1e{wYlUFd zC`YNK%=-7!wyY(NzKst^hMm$9RVP=CNEa}mBl+t=J2AMMr8qFUBS}e7{;^rxVo?Zs zdV(Mf3=J^k`nf&vP%%7#xE<+2(qB*3kkQ(GpC#v+Z7jqK@cwSMGzk3NC?0G-mG$sN z+E<+>AV0g9IszX1k2{8oleO}QQS$v1;T&K>Nzy80ROQl z#isrdBq97qW`1>PO(t|AUxv{mzVw?gQ!M=#sA~Xs{i5VoNnkp32jp+CId)Kwy=Iii z4pjbEq84Ud>X^tYLH{z2vw_tEmCKr@VleDUe{P6Fj*o{TJqUghNkr-19w$U%kGO;< znt9%J1T?rg1keb=D-31tG5GB#BZBnxNZQD~uAdKkNgQiE`Qxn{sCTh2x9+ov*@&Z0f4{jF&WyVn zH^JU}=AQf%cxMl*!JXioAtmD1&Y!Qnm56^t0q)BGoRrQaWK=zx;SHNKhIJ)>MgUk_ zurEjM=j5jDNd0tEet#1(@iX6U$)53MQ-*?LG-;HLAJ3JIq|Z6seIik!>q_~IkoR?& z5NL*rgAa5>)~V}C_>6e%g}DjcB@sKZ>%tf2rql9GL9(Y`N3N$ouT)R}Ix;=kxtAIH z$@q`CkZ7GKBg}Tjm!DXf?==u!0vg!~=@v-&lScWK_;zvM>PUK2OB10sk~{q_?gjof zv(x|9j}s>oJ)?kA$1MOuf!yTdWNa#4e#Ii_T;)XB1hN2seKJ0geRI=mNQ4~O%Wor_ z(;qj=q<$TIQ{TkZ52nNAx#*o5^0|o6r3WlzSNv+zReVkIwnKRT|gi$?#h8>~8`BA{y9)9E*i+&FNLCSi44LOR?li)uiZcO~HY zZtT;zxGy4gcKBA@q-(`(@#2CCrS{7Nb5n186Gqkjlx@|5OQ3QjL`2`yEx1(1+kz_# zsXQgP?gOy{6kKi^Sxtl#T(|TI2(I{gLbynBDV~s$D@#%iOD6YSlIvz^N=dGWa_pM! zEWq(>f=f7U6kK6k%LNa?<)#IfDG*$K9fHfBm*CQmLvFcijG*}9``>*kB)4vsed`A7 z!^~g1YyVN@hC-vUKE6X?y z5neI5*Y-9Y>;8Hc4!TNo(w!;6s-|bd6v}12&avzBW)6Ryykp-at-e^FFnWZ(?}pKv z+`filo8HevZ__Iqj2=nDFnWAx7)DP7!{~LZUIn8!tu6^hPm}`9lPvsA*c1B%&OTmJMX?;Pho= z>2bhb&|#X`+f3Xu#oT{rnfRh?GgdGCrYJ!)q9HF32;_BAwPc29j3&2@V?&P%x^QP` z=uvw%xRY1vIv0bZX_)XM1BQt%K)pfLzQDx@0Nt1Ivk>&z1*rGAK7iPEAflujF-nwX zfT)N$i?RHsh(oV>t(aztL8Bt-tYeCSadjf+w+jU8Vr+A9ErO_eE3Tc5CykJ{>Po># z?z6*XCtjZC;INtUNJDpbQsl|Niq5pjbKwDEMxpsCk?04A80GgFo39@*V3d162w3-# zV$h?oSoApk1o_YyOIxf`G_9G*U|KVq(X?MPjoGI)sT(rN6h?bT*K-*%8vKM^qdQpq zOj+>azRmW@D~*Ph9=~1ye-~_Q7_e_p&0#r=w)CV4oURiv)g5}^d0q6oa$yJIY5O|ASn1dg zbO1(lSPm11OS&s8hXzLLupCA^<6HYhTsgI6IV?&IHmmr)ev|fN&aeHZ$Z{BzGvjqS zEQblx_Ta2eY^8u{vxhisSL>zE1e5X}U0XZ+Dc837-xHH{20BcK(Z+#v zSU{!P0?udNyO+Sl9r3wH{t^l>mtUC<1Ahw;Cc0{wQk-iY5f1-@qANtbMM@EPU$R&CzFilFee01;dt95bPotX}#>8C!T;f|XolTln`= z6pF(L3_Q*fmz}{OB~bfBJ~>5pC&sq~a!0apN$fPYJ=+7?=M>p0U%vf9AaC`p58MUr z<7GRu0I)9I>L?8T2?|U9h~;T7agzeq-IUDMKngNTBc;ghDE;IQh*R|w$;(Y-o7w8+ z=X3xr$%4+vin^Pp>;T^`5BrPLT$TnhtNK^qvm?eyKg3rC9vHqE(4` zM1f%gYo_&{_%#~UQt#=HZQrT*xLuQ!lvk-f{sdK@UM5rF8>t@z%TzxtAUTqfj~c4v zW3r+&AM;9z519EX&BxrP=HvNr9+4>)3t{8^Fx!aYE0|vzl|_`;Rl3#y(J#=ebgc3jYCX=lrl> zOxd{X+dL-`P2X#;XJxl@qQp$SW&cclNA9GaQaCi7!OE6(C{Lm#ZxlrBL|DAKlaYab zB0ylqhgS73(HQ3bkO}UN-zh_pX|@#UwGvYz$S_pzY^I`n`U*u;ktzQDTbA+Plrj~W zL{m`|wdKK|rlKeVOcNVTMG3=z4Vq#Cg)TNz5i69Gr6`Qe&)Ob{)Lj~z!cugfHb=>N zaEn%?Ng?=ei39Qx`BjbD^Ua->)fBnWWPSo{TOSz z>)cl%aqXJBF&nE+{>;+fWLbbi@Z?7#I`eqSQWSBQvO0$wxh9{MqOawF>}W;|cf-g% zrRFA%+`A3cnm}^TV+$TJJD!Jl8J$3KZvrff#EFZPVabuElxi&?Jh4zus0&H%nLbc) z);)|7>O_)zrp!4BB&Vivji9jhZThR{=8%j#5&Tl*@SX&dh>?CC{mXME>k) zrNue9ZF^uM5ven;;Fw>x#>fS|Xtc=0f@87XOJ`z1p#9UqfV=_`oK}EWaKPS3H^mlJ zTERiAi0-t4gEg}n(+Z+UcCTYS+8NpDV?8tWqYz#_!fa9=>yeMIp%q4g!WlBJ;N6yy z0C@!_IIqAI<`u*zTABCqM^3tT$v@T>qcJu5IX3T8Oc^$C0UR)%&`(UC@yIJUmaj|9 zD+rcThsHE&xiPWeYe8a>u5x1_vmhuZ2KO+x__;B-%q%z%TcQ|+P-HnOFD4O8={1P46f#v(e7z0^Np#LFwo# zNf0wxV&IG#r&QA>+Bg$bcrn2enq0CWA>ITTb`z(-ZQ~S}LY#v0@Kq+OtQkI_8>iru zbSF;1*}Q5Kr{E+eQdbd1aPMWx#?L}2AVDAn69_w9AO*v*4QcWFo21rC42n+I%rm-%gGY0V!HT~* z!mg{s9j86^Zuc_DzuWEW4t-sWiSaJH;t1stACq=fL@TpQBb|X^r3s(|(1jlv#tMYN zcj$VAFg^YR4_p6;4_kLbSU~Ei=2t_h6`;ZT2{MB^<;E^2<(i`~Mi$3mto)vCECJ@= z*a}?~;wxQp=M#SEVs4Xdt2r1Nm=X*{C2n*OTm(_9v*Kb2V?V*737yp-b13cOHzv|d z+zX9Gy_`NKyWbdatP+yTpCBZce#3}d?gaq3+{vZIq;V{-H;r_0MP7f_st<@$^$W?% z*VYFyTYdbTj+~%`*UueKy8is5tZMlew^f(`bh_94P{3Fp-(j- zLcojqg1P1206$;V4KtkG{F)L>Q~735;=`1N5+Bjd%=?BCAMq9Z-Ao^;Fk!u>xnK}- zpFu~tw0Z?0VVV7=f2PiwUW79#&w)h(#Q+sP@&G7E|=O-?Ev}dgCs9}Fl z&*a2Mo%@5?Epc@WgllG!jnEX`)(KFQf(=`;jud*zW-lSYiYQ^u>DkwZD=ja;>(qhv z<`w|T7I8mh_{ka(6`YzVQOEe%G=%xsdbC`oV@QhwJb{9X$+LM|QOT1iQmk(|)w%4px2O1-1$fH^F zJD8w|t79=Bj{!!cp2&Ta(eCcoM_UZIzT%ZLrLsbLULPdy5x@4EB1LIlR`H&dImWD} zqRr{d7w_H}@sQ3ik|$7mxC!PU1fgCYA^UK^p-+W3gIs85g={*glbSuAuTj-sR6f%P#ZIZF>AJj<>n zN?moI_LZ4o%4XN{hb**Av0dw_IK;GTz1v70rCsYekSc7m)^otuC$m<74IO31iZ*ma z&&^HfE}OLivb;GCJ@I!&h3gVa=&VTgXvRDe?4fG}y%_)RpBIVf=y;JCS zepub_g962bp6Dl_y{^p@{oJ-Raxl-Wi!=E1(5pvWql4JU<%tf`Q_ksk`=rf@&__o} z074(7EA>a7=x9FAEKhU{O^b!dN2VD02rvD}7oFdnxNZUY=m4^wUC}{E{2$&66Je^A zNRRfl_IS+T#l6NR;-B?rZD)i7Fltc~qFECD8BWZHC)xyiqD`SE`k57A7F+)zuu;j8 zn8mrfqMw^9j8HBI5#B9FV(jUL_Y=exlPfw1Ji3W}lpM!lk6F}mBlgkPg2W=d?D``1 zQBZhAN${g|`QrFW@S|f3U;_G4%+jf>nu%%B?n-o{gJsz2#Ea;V${kI14Cds*6aCzF zQ_9`98W0hc|^>vv#p@EpmMj06cd zpzd7i)&LNU+pmFAqF2)c3%Xu>5r7eS4qaHbgBP!N6OPn~*2JGvzlzY*M?RPY!^os& zHWU3KO+_U9R|I$P1k~;)0jSUEG98TVE5d~OMFIyDm_?EGIEb3nWCRj|MZg}t_evmG zQjc;8Qi?B1|_l_&~J^BeMWL0$MvaNrK1r?C?3jo!CS%ngIAv)58S; z2-X+?UVqYZ;?F<4M49-aytfLe zl?%HmVKpufhrk#oZW$MmF-|Dc1N-AgCdWclO^=T*#2 z=nxm}i4!%wE}d3UnjzmebOz=&&#GWmMk!o6s*<-@*>UiDE#aX0T5GxdZU{e}QqfLi zdK^+=d1}~aaYkifwi{bLWnF+0PVAi#y5(f{@kPWHM(tb%2BBM2fA4>;!MC4WxS@1p z?=!L^mUc|$g`+7#vrlaFIGH9x2d_Gq7E6F%a(Au^F)NpGEjmSsVDMGgubx=UWi1Np zb^D#0=2$;*(;8a@2Gd-H0K!}^OL0}=yM&q_r52A}@Q1DR+S>p3dZ^7@3ctNs9rr@? z?YlW|d@~|pZf0m-ioFeF)%W5~(H)_;0Y&>j6o%LIzWp@>-rhXJ^>>yEyigV5PTW0& zZ~%j2!p(Rg==Ns4(F;MhZYt>31Pt)?-;ZjIdB`T1v3lnhfhN5OYfeYlg^1gm(&B>a z^Jc2nrCVYQ@x%w?8X17Kxq=C#@z7=99hu;?=l%e<=aKw|Fj2AQ`@t3KLY!^PRw~gK z;%q&g4zqnu$_qnDT(Hr3ye1gBK(B1egrsID3BJ?}B_V>LB=kx->mOkZDN$4e_vO?T z=zma{l;_?Kr|OTO zc}UsyDZgdHq7Vl`G20iiLm!5VT$vjhaw_gA6F(a#@zX=(5 zW_1HBm%gzrI2QT-T9TPT!bo~+97#Az<;b7COXrAgVdyUMoiW|dOKJx&O-895Ikdj( z9YHz!xjoVUSn+sakzZCl#J*mtNA8nZx<~G$=TrHJWP8Z2TPeS$z)kaCQch|}Es7Hd zG5}BP0W<~V#^l{9d7KNJhjKpw(J)#KCD+xn4>p$fVah9!0kD}?0yv(h&iO*uU;*uj zb4xf5nq%V*emAud$0m3UJ<>82)L*!ekLBMetl`p@PLm(upE=WJ0Jv}b!v_-iiMR;R3DK{xCp^n zVk4o)>w$n#=PzCWpGjL#j2ngL5?^Lg4oAJyQ>RcP29DY_L864e1RqFe+=b|e{E>mB zhuxXIXwW{g-t76nzb}b!5s^)|70%FgZI7J1u-wRZx|rM+>J?;0aPxs!S&1kT+-U zY(SR0ko(}K%RCoBxetMfft{q>hobB#8Tr~Tf^r{%fZT@*xewV_Zk5c!Zw!99l&2qn z<_C5{Ch}h_7H}FWh2_R;t6W(~)>~;J_u)eBL$qZsL+(QWxFGl8LI6`@yevqKI2^03 z+=rqd_aWLiPMP~~3L^b-{o*_-V5Mvc*isTR%ST_*MwI&i?!8Re_^I3n6Oj9m04fLM zbmjuK!#8B+J_HF@QKnl4f3N*?xWA9W>!!g{Pb}s>T*!UMMNO*$avyTt^B7C`g0Srk zoTk`oEM{{#_n}D2eF#e0nI&9;ft^ZiZeV7BUCMn(mR^($Pa^N!pDIlJn|s_kOt53K zmn7uOL5UVV%OE`X5pKUIUZ=4H3Wv7=qBy@dim*LScD1lQK=DD}Pb*7#&}Syo<%2#v zzL~;X2Yonwd0WqeKFs9nu8E2`%Gvj+Y&Sy9e9$*tW_i#nLb(q52z$KioX@byRp&g~ zALqOu!8xxF@R;{6_?UMebj-V#Jj6W?AM+k5pD-u#m@iiOm^U+d%$wOf<~T{AT7K}jhw=-JdkA3t)p{h()jxVB_d=Zi$oNVpk)b)=xFQldv zN=;|{#W!iQ@A$Rf6dB6S%&>c4cfcgFSPD&LF&=nX3Xo_aZ~PJ<(G3HxP!_wC{z4=Q z17%$^&%}hM)DK*O2>IqJATB|8w`9Oe&CW}JxCCE|MBpy>vD?i8G)0}KY08vZIC^Vy zNSbKlnO`hknqqtnzqM5o4G<7$vR=-n6yC`AbU&J)Ku!=|wUz_W{g}0uCnvwwa@f`9 zRckpd>q`q2$S69lxR#>;#i-SNW(m?-%2Vg4!Z^=)umxu@t5E(GSMO3r6|ZFIadd=l{A!hTV5W5Fuf_q zqH>XQvl#Su5xZ?&cFgdgity;`+Mba3X1>e0B;o5<4uqMw9|tvY_4BZ28&R#K;0BiG zv_FAFlll?mqEtVSty2ADzDo_orb7>wsDa?d_6hN;exwtq*!>X8)z8mK!9?kLt%Mg- zP9(fUJEY@@sNVPp{#;D&C=hod-6eYdFk?5=mKa+a z@?CGl@9VZ z1$ncW2YMovC5Dl=mQ%?tG4yL~4>VXBPUN}-rx473D%T~M2fuElx_F9Yxb>7N%AqJu zb%}4>w8S3VT39*<*Fm>Sj=EN4BXr2?;8%YCPl}FJo7ho zMdLybkM*L;z-c_6-PFo}(Yk`&gb(xs6ISPFPjFm+e6DXGvP zT1OZ{FhWu5SB}7)7e+Y>-=)y=T6yz;8gG6_b(1$n<{w}?>MwYEoY6m`nu zXk}ZsY;`i~I^l`;j(;Tq_eSD&x_t>EbEB*P_cHe*Htxb2xp1EI@VyyfA17x_=nXWh ztHF(;%C!+o$Db%W_7jT&2y?IycL;y8E)IbX@VE(V03LDyA2+cDqQq3r$AYOXcXIzj zGg|MOF73f=N%hSO_qz>~*-h`;D`)GHpBrduA7-W-**SIXiDB=`;(cpd6LIUlWrrdF zsO8g}l8fwtOF`=8r^G|moIoV|bkUnq;xpnoKYjG37|Ypj37OagVL5Mv^lCo+!%rXW z`Dnf4+ssK0ekT9$a|irX`TcDhJD# z`)G<`^=mt=0!qqfU*cCC4yY5F*U7UVKB}(CU>Al|+ zy0u=}!OQ}^1nG59{tMFUuu%?qS;qram~O3L%9H7er;z)IQ((O$*qXYcpNMz|nV->? z5;9;KP9!?n$)m>yikQ)_<)$IML?EJ90uJ8L;4(AmATpE>(p(M$YG5LI9m_G6iRg7a zb;mE1tBY3;L3$NMm|mg?)a&G45vbQepjCLbegH>yRwQFGShm)L*pnZ%K=@eqSk92> zdm_ftuayjEFytvf##^C-#0JzA0lRI~6@j^NM@3>0(kqf0c;`7fHzWlZcQ6dm>D791 z;vZg$64C3pcUu$I&aO-(E1q)0C$1RgF}+&AgIl{-E7rY-lJ{us10i~S>=bAdX#+T} zO`9;K#Bu7~<9|}5P1IYY6oJ~rPRQ(`X!9BX9JIWj?AkziwqB@&9ONFdm@7yYzx0nxISs zh}-<+m(N_C1k1xN?Ewnr3}OOV#QmhWb~T+B>ENRZZyW2*qb>v|m8L##YgjMAF# z{3!k}KRNLLKw2+F0~@6Ef_sxzq*QpWSwcGOD|5eI=J_*sNgqe=#5Z$oUzbNF1hdo8 zJ-1lcfR6TPh>a7(%s5k+85f^8VS5E^+*Q6~vM&PGu8RN5k8R<7sk?2?bc}s%k|#gL zD+fSYL+VlI<+&Y?GKd}1sKrK**01FV*f^Gla0no*L9}QnUjX;DDHl2wWMZ_Uf_+5* zrd%u6c#ci}jYoKZxK8N_X=h4|ixqw|D zv)S$wg^0c_!WTqMarqqqAvTbmMYB`1&kr-y6-C22HayY((AW>Cs@6)K=Z{?0=h2PJ z`aF7hS)V5$1t?{@uTKyo%0FTlDRqN5Q|_gZsyq;Hc@LFR9lY2+vBhKYH-eq7trudq zdigmam?Rd|w&C6s8txNNX-r%06M^ZuHp_kfZdMSa={~xaR}32FW3(v(ZMqLi-zdSJ zhlcsZSxTkYru(2Ak1h8(1>>^iKHZVDnG>lBl-+h;FA=FKzPW6=k7i%H?LH`ZYd?)0 z*Wo&+X+GL1y==RWY0HRbKf!nd8t)T)Yh1hWKKXY}TkkzjlDnQa168#;AOuv^7Tugw zhvRn^#61?nbU-+B;NLyP9wt&%ixktVZmBuAE%_4saPVh`L;XIXGUOF4YgA#43aKjU zTG7FTwSgisG=sWXdy53ZRN?iq-PcbTrple5*6B_Ptxv-!m?{i)dqk`d;NP_2NRWVH zC*c#o?lRujlo+SVltL!Fzw%cC1hOoyVf_4;xy1;R32W|+OzH_gGTZ?UcrPMH`mxtrJ?s60w;f3M7oczc1F*%12`Vw< zwwItI_J^(=Wp4)}8D>e@+eIl5&ga9i#q4dY-7su1%^e8FBK&7U7;%vp4j~>EP;@fl z!$x{3Oz9as7?#+Dk06Tiyq!y)R{pK6Utz%JM)4!)soVee>Ztsy-~IQ~0nbn!F+nf+ zmET`63-pp%V1lAW{7NNT4G8lrosyv5R*Xo1xUIV$z)dM%f6r&Y7F@LOr#jh@Mw|xz zP(wNr_0nDSK8dy_RBsc^YvlfY6ZVDTcYYCQEDTVC@H5`96?VXC#q%xjKs)4BI!jXM z#Te<=cng&X`I>Ny#F#Q9%&ot{&~r@@UeSK&ipkXWGxuKl5%X+~z@>=^h@-Xoj_GzzG-4w`_U= z03bYP3&ySykQ*bLp&gA(DOM}<=R@81pJ#^s#@Dh8?m*~uR?PvILQ~InIHlgLo*oyV zF}SJVM-vix=;gO@FY?EAE%LA9Ql!3dO4;?>r>2IczjNSR9%hVIj~Ry(lWcwE>U*!k zuh~qfgOPC#8qdEU6M*9r{k>7`C@uq03he~55N#U+$m)wXz=p<3buYQua%wt@BjN5D z;Z@SOzi-YZ9ep#pY2=-Kj0Uemv7}^R5H%#ig538|{im!ex!~^<)p7;D{ z#xb!5C5G-{0kn4U>+fa0@@^fi1aw%inr3Ix?qY(>E_wl0m-yO=Kl9hjup+oQR=kQQK5YGWA1HswEb5K*37826s zn|KZnPdTir428NjfwEAIUIK;jSrQnL!4KK1Gb4h}daDN{paAHwP^BvucwT#gw;gA0 zTCBX3U|;!KY6*cbQuR-*AlGmZI^rC{xDrul5Rf6W*5LSMT3J1>zPYEu_8l=jIOOAi zU8sMpX3oZtP7r@TQ_;oS+3fRV?=ja0R1Vlw-mP%5Vi%d#6AH%pW<kr8r2G70-%t4P5O;W z@BTX~!N0U+l|WelHvD&1||`IcXB#TfLAne zx-Sh!tCgccJ$L{8g0h##$9+;J=eJ)N%{gZMwtCN=sp*Lj0cRuMe(C7+_jR=D*Kzc^ zYmi&+y2KcU_NoUoa=uQ+_~YUme_UK;-?I7oIQyRAy$V%R1fP9GU;9n)+4nGb?VI8$ zPXd^VB@wY3Yk9>y9m^H#>GkuptbL;O7ATuX$^Hjc%37@f@EAPR-?IqPk5$cxSlczt z`>}SEdqrmfcTybLuX;d( z$J^(Z7Hi(|$H`cB?a2C#`&J9MfpArqo>`PLC;z`hbVxJF2c>5u*hzeb3%K!NMtEYg zGw>za3}*6I*0t{InS~P%0=7SRnSFa$94!k~!&(+uX-BmGau6PZ-o4?z;_To0E^rZG^QAW1~2{SGL#?v;a%v3U)6*l+B>>+-o6f<87bR*rsc?vu^oEB zi*$z8BL~$@BG8H!memRMmlg#t) zq}N;LTMT5sDq-kX+a4I4b>YusW*M*l#FCf^Si$Sjw%Q0l^R*rUB{8@OjX)EWJgJF5 zlOA1I5)y$XoY%Bc5P>Eg^Bo67BG6oo$FY*?o0S1(-Cr~v6>1AqWkJxSH&Qn}b8yyW zDYwakV+QuSg+=0lwan)xO@6c=I!X zH{9>f4=vjUgdw3TZc#b~f`ITM=N{UxF7VlJ^P>pti0rv z5#aU2ZOsD0rr-3gSlA@XTL~MjHp-gM)d-x3o9o!3CZXuh5;Yz8=oU47ap)E`odD?; zHJMdDN|LZ()h25askv=Wbei`iYkIfUhq5LZNQF6NO~A-scgY$$JwJraShj@`lV>b{ zH*3L<4AX%|ms$AJqhrcdfaP9|UZ$AaF*DjA+o8DznleAhTJU37T;PdrNRh`?2$Yk>b20&DA~dAU8JGEy zJtHG=`+c_d&|WsB^P`>T)cah>E#hCfGEeVs^&`~z;FJ1EbAm@=^FmM_nQZcjo6ci> z;fAcNpLjAPoW004+>_bM*G8y;h@zqD6JiB_qnLGb$C;ne%Z=Ti6Y}?Cl3)$^mtW}{ zC4V;W2T=nOS;o0-AE(Hz12~D9=+viEl~cgxA)~g(W=dmHaM>vOalYzTY*2JR?j!9C z?x&6`jigSrNZU`RxgY5XFjb!@l|VnzX!yx~Wcyg^=BwH@N!A8)q<){A&@U9T(1%y? z6TRH{{W&8@NN>JNTu}Z@Ia5?3;WPShE*SlW-W&l9RXLHK=Et zSvD*T?~VQ-lo30lC=@SeXCCqErYG*qB5%=#pwo>yn169EJk&sN9yK68MLRwJBX65{ z1A15okk|@|h8>Mxz*g&;w1wE;pqy#qM$K!aZo+@?(6dbK$Zjw_(GyJ+`GP&$7w&_| znXT+_Yc8w#D9W?S?PU-JQQ|(8Yr5gE4in9ZUxiQda7KUs!>yL!9x)%+wiOq1MQd%u z*n8N;{Ro{}T3z>nAfC!+Ju)7gE0oJ&+_i+=_8K>(GavQH5VbVujYShKb=p2O=?vnD zIf{=xQdhU3Pe5{O6X91pICdk9 zd!ehTo08<~TaxEi%@fIwjEst|PYg>YGw^w)dI5QH%RHD<(5)sH_=qT4!1#n%q2DNO zWiMVELnj4@82&jWe~AD9*QYVqOfd#qq>r;Jjiv|43}CPc0wDEKC|sGoOy!z!BJwDu zDt#~8D5-46)_zRElphP$(Zpbj+~Kj?qElXUI<-!DZ}DWewc^=r#`Oq5*!%^eu=zK< z<=qRRu(_YSlzJpaVe?q^#O;rMFv}=xW+zeD%xX1*ZV2&;vE_dUJ9gjX8Cdc>j z+@5MS?IRJclnQ)5+fj(}RX=h14RwOc&z+>Y?O|9Ue-<#^>XF6R2z}zVp;b24OO2IC-fu5tn9{Xt? z2w%Eeo}N%MxlBVOMidNKf-Xevr_KX~g6$I-=N11X*(4%=<5jiOwC zBIhGMKo`So{0S;#JS$khD^-@nP+6xSD3(Zrp1>)|-k?Lag zTz}}0$%~hoR`x^)YZanV7q8^G+8SfP(-QgO{N-#!jlnpUo~(0Yg*4WCuQnzlMceiS z)W@NH)pQskNv`w*W>xpB8nm{LaD~mA}&@Dpcawoyq{V` zX3TF|M6`D;B9rhxw1~(n{?HUurbV#5YZ3KGY7zAXXc6@r zt}^LfP>ZO3GL6$Calu}XRZpxgx&biA_8S%*CL|5YZ2A13A%PIB0sQe5&0V* zT156@(;~8$n--C%0-st$&d{bsBqug4f^)QK5f_VbNQ+2r?^?w7Hu_6220A_BQu!}) znq_MMe7GqP6h8n8z;8c{=6jj+z`nrOI=4RY1rX-;i?IFFt=xex(oY$6-R1HzT|@>i z`($FA1V>2$7{{_bEuoIuZgR^+QUx+h!6z|SqOWIb59ig2(*03cQxB!9;#XumbU$ca zbU&%R7$LH*j=W6ORXv}ix9 zlK`hU&r$!1k6QPGhq3!9siKjT+=)!(xw+gjk8o~ZxIC}=jng`6Kahss8JYMT%rBP< zTq8LxSZ=1K;}hb=Wu`Z;(DW*rp&eSS8U8sXe^Kj`ZluTnyjE3qJXsGuD?8>77Xh#+ zB8&wH(P)n=GdOaUcR|SvW-0Y6#qt0uNHjyKbF%@E?^TrW8nt@8r*F}pAN0rJ_mhyh zG3wnwXt29Ks0+B7WkOQ#)htg|waqbSXlM}tFD62GXc`>2nFGT;6W-gj2u3HV5tU6g7-5K0pbKypUjWf_i6~NN%eBBv^=>Hw~m4;mq=+ zX<5!xua@(j$4OsA5{iPQ7nr?JKK4w6@QDcqzJ0p+(>opO1%EyRVmE82j-2o;4od|- zT(FNaTpE_s%mkuTLo&>uSF?o*$Dx99rSN+<2iAU{xi&Fzhf0m!NSNt|l7I3h)(NLP z09L{F&tDdfJ$s#R^Gc;9!XS+95tz?em87GKzXfCezGn zQyG;I30Q2k1ZMf`wg(50)c-*+K($_R{tw$HhaSbQoH5-=Kk&AiKna~ea8@;%bxXhsW^(1;Z$PXFR~CwpM56p^ zP!O8nDohYq1)hHeAslRk6V4hMH-7HK;}8uCkS_7TxVOaA474IqYrtXHgi^64G9vVM zq|r1$R4mlhjV1spf>B{hXuzlNE@(_r)4T|t!h}Sp_{nmw5>$ar5fgtx4$KJQh@T($ z0FKS928*p5++hkG#mqt--4f)nDzCHTQl$J1JRi2eg#u=|2vcugzz)brs>m;7@Ja_F zg`2s`UU(i9KvMX1TtoeN-9r8IxP-EIFNqO%-$`&B9sjJMBg)0BQ4|b#hmfpLKFwMr zYnk;*bueM8-}}^|mKX?OFC4?P%yt36nJY{@{7qnZ`u*^7{z=&0xKU{Re-QKrGbg)% zz)Wzan%?+DP`tYck=-BlZ8MbcmD*I?&~9FG-_7Crwc&<~WOSq_qy!sq#XqRDw$ia6C^6wV`3Vz%mMB8F7;-NThl<%2L=HX;%74OG_D%z$v0mjy#NYQr3owBUERHh_ zo%cf&`MFNt5EZzOYAT{2v>(5b*wq&zA-fAigstiU+flqx)6c>vUkfDtT>&RHAjybz zElLf)F~IbHP>gl$x0JnPg0QYPLdG-)Bo^;5iFMtwBrpb~w#Uk!R~=K)wZ>yBv98-iQ-NbXqQ2d!3)GF+`kXyNUArx) zYZDB0-5AJ65^z{xKQH)kPcba zTuXz@Y+Jj^qlwZIdA||0BaohOl{lb>222k^)(-^H(&UtVyfB-H=hrBzfL&OZ=4*ST z!T-SZ(0NFl3VG?B_XtKub;6j$dl@BZf`?(sd!XamK0%+ceq%aby`c&%czLRq8?QfS z~|<@6y)tcbk+wv|+N?Id!)&!uj@HbN~nQ8-qAg1qpL zR1wGQ#%p8f0Y9`E{y8P8cQ%X|(EG7$wF9 zdfN)hOc zej0T6HAg>1zfM|!pz)6MTWVFm3CG&keW=;v-gd^5HT^u(<+EzhkBdUNqMu@hQY`C{2KRMm^b_VN z+mwL8&71mBgwq7K({I@s{q(hs5bVx?wC=RBjiTg+_J^Xd2${a#nO8Ow%oA48Oe~vO za-X)g53IV@{({mtofT6tx2CeU;}vf!5O%qyEr@G31M!r#xb{~;ToQ*+6F zKD^mGvCI@Fmf;Ft-v|x zORy$97rh610*GQ=S!MIK@>vNSoiY}asKv&#vajWcC3;&u=2a{xxEf_z*}IvjNS$eA zVJGJu1wmUg+*pwPnN${L=zCMc=BRrRBz;)w*R?$n#-fv7NPc%89!x5WO|WqGWl~ve zl1KW$MEhDE2BNPCs!>-%Q>+B9vax?t>S|~buZC$w!gMdj6w7SH%i)sygqOpWJlQUX zo2{Cc!_8QYCy1V;%i&@w+IBe%TN#&~N;784xGslVTX_hg)Npg@zkZW`HU-?T{if)0 z7{>3Y0PD+P^!J7c&CB6pq($8h7h`92J50E#64M#FJ!=Cn8)UZH5@Ei(ct^{$3e*qT zZif;2o`H6{9(oDzdib?Sa^c)SsNb%Kq7(xnaPR(K1YK9r!I#ZAqCTI)jscr ztOUlZA)G@L4vIY^Xo8c`1nm0pq z?D|9J*+Gm!*sXXobXzw=6U>~Gfh}K@4;JgCUlh(1>iIQFR_BsF&HFQRNh50Uq^p7!_aUM{ZQh4)Fr9Uf65!+7 zj;lxZH1FPr>ZYG)G(a$+^xdXIPKA(irY7~Wg_(njmFc9O=}IS!sATw7m~ivlcJ-VO zB5|SfE`~!dh>RvU=nPB7^&Gx3NlW){`Zl|7J8`0_QbWa0*Lm zD}zG7f5S!Sz^s$8uZ`(wC3J1}x(^8)lXv$uXFP8%&13MX4ZK+E8ok=E8Tmm5lS;pLp!6O(?VLv`d}8 z5y&dDRWCdi_R?+75-6{nCWKDv6Tk%XZ#aD0y+9PXJGr!&G!ECC@JLzaG+y^n6ef*jZ6vtqopKrfAMb&~7SF}?e!bG5c(3E1J`azTTo~j?T5Z2jMKj=ns zXIFusn}#&se>2>&w|b-dDkyT&-@_V0&Hf%15sK0ZTrDgjWQvOj;iVtl zf1@o;3sr>TcjY@q6`{twj;1m~Pu7mNFuCZJEnyPHT3*`&C`G30FN&+peN_vTsH=z& z5$?K+2#HWdghWvhp)POP!{WfuLaK&Pq@=aF0IL#@2vlCI4QhLW(}c65G-DSqm98=5uwo3du^|-jpzNmQMh}B+w?)fEsy&-H^aT{=YBfyi3>P(6m9mopECl_`xj+1mN#$i z=k!?g?Gb_#Xg>$o&@3<7->i5ci&yUFnC#4T*M9DkH$(4dQ@o$?Qtf9UI(a|$jgi>T z;cZ})Rqkh@9%;LF*aK@j$8|9MIDX>D8adw3LFd@Gp*QzKZs?}Hr44O@H?%3-(D8|O z){c%JIX-lQo`&&lM@L-dx#kNQp{iqhfSGpQ(DCDR3UEUw%WFS-!Erx-Ey=$u z3Db6iRuYbb_H&iY$J%C|S5yH!;cO|l>wUphOg$s8q`q75@Ct15;!0D7;h zDt5(fv#Mf$%G}%M)>c(T6I@j>EJ`07Ghwff^Jz9SW%pZO^J3(+rRYYX%p4NPJEbw+ zIEN6)0*PxWDVhP>eV3vUj89!PrRX4tn?&Sze9@B>&5-e@9+`)%I=TDiVLgg-I8R3e zq!0~5__a16`X=a>LLq93g(zMsAv&>jBtqj?=edi}=$2$;h|uu>*HQvfk@F=($D;n~ zM+i?R89GL(h>)QJFxV2ILpqW~s0kLKrci_q1*B7i#&3M+L}L=yQWA6!omB#IJRdRC z82VK*HCT1>A`S^UPlo|gf+oLZOP||nqX_+4k11!S6f4Wnpln`&Xya6;44nkREg3ow z;#x|EW`U0K1LWX#w?Hh=Yl|t!1$^y7bS%e5QHTZy=UqZ{5bZ-kbj+-=wDzKd$_MF{e}wnv5qXwKUj zx><7X1*Q!*Ywo>39hzg?duvhI_1-hZdk-(w-b9zY7>&eviTPKko zwf$n&>IM>%2ASa9mylNb6O$9K?H83J7$?E)mn_%8KWnvd_kFHLEKrm@+%{iO)>)e` zewH!Rx%t*`U>Y}HxK{3S+kD}DIZI)=6cAs#FVLeDoS08s0FUzSvs=i!FBo`?+I{N; z(xZ0YI-TI=?ROI!i?Jo|(cO#Q>V=8B6#x)r&flZGZ(IGTYDDfZ=Pd$bu82b(WrVsE zh+GpFvB+bSn_;~kIIHIqML(a*rcoxJ2ao|(yneQ9Vz-N2Kib`S=}qz`rS~ zv{sb&tVXtDV8YYBv$E9Xa}>+Q!{N;5so4$lJtud9A5v)HT)b|jEc=n0VV3YN2Gg}W z$g)VYZ5(e*WI_^)s8>UE!h2SN7P%t z2z1R{nek6!=!$)q@G*3(hPCwF{&cLySdrC0lWW#u*-S0B0Cj4EB+SVE2vMMNi(yV4 z-YQsT^43Wa{)z0@vAP@4Uwh)J({t@vh&3h@M`Pt0+e zk)KFN?!~z9bR3_S=m@W$S8*;tRL)Hyd!DZ-jrS(X(kpUWM_W0vai+B6@>L7R&RZX# zpvA!yZF%X#D$n+AxUWZG+pkZ+_Fq3D??82f^-9!FoxmDM9nu=ApCBvb#3*jb&0&7h z5n_JRQSvp}u>hi{&}e10FA6Nl+4IIEy!RNR-6(fqjBdw4;18Iqquoa3{l!QobBZ*! z_jLB{ftwLl`F^g-``7~ugVJcvO#RJ6p}E^_TkS!4{n)VYPtfr1AF*q|-JttHy-Y3W zwT(k+QH>N7a>8FIUg_#`gCj^tkv?-ezO!0PW=kXP#T@;xa>Mc@=Kr_Qm z2t_d3iXjU#L$rC#m7O6-nW_gnLp$z2n^_^6ciZju)h(MDisX8(eyu1(mmsAblUHMy z8KOO|kMQ^)$wLs=g!fN4&MeFf)ux%D2r@IYqi*kl?(pcDf;hFR#~=EM z;0I|)a>I`msnm~D#&d~a&&fp)h&ftC^=0M$4Oo9huoxwvTg}kuN zP~4)*Kz6>YGZep}Gt`VoXDCY5=l)1;dhI0pwEUVW@9FNSa?+JC)Iv7&vO%?|4bJ`2 zldZQAJCHII6coy0&QNQ-z$0bI&6)=iGGQoR0CPZ$zo%I}AQAHi6oP;=A-cdU|1fN1 z7h9QRJq9HkwD{lJ%(y|AxAVtZFSj%jhJrE(YZJk#(Z+_&gdw#*6i`2cgrWKb5{BwW zlrdDjSO7<;ev;Rplks<-ALR=*I|8{=l5LEEKBC>6O@@yI{srm_5la7ZIp+QaC#yZ!m6C3SSIbmhxhIhO%yPJMcc`B zkuX$*T5FGw@J=`eF{ zbL;XyD7U$FrL6n4KE5}9Cz7z!n_sO{v8fZeuRYK$DFX6ghI4cXMu*C9Zc|}xvJLMP zn1b7wK1Pay0C`x4qYWTbnumj+j598WA)_G7!!0b|=eEhmBi{q9!%f&fd?4Ha6Jj8& zip}PCtG)BI&nv9M(Y_BQnnpM0B|!q?unDpa^OYajhVzpQgiG6S{;G0H4}9aG9tYcS zbbI#l#6%mhlh4MAnegU*L?}Jn8Dn^8+!>==8OWV6h&3tWa1mr2E((mp`H5D#RvTqO z<%t`!c$aNB`Z+ewOHA3lg|A@j$A0cl7IBqvIQTaeh6N!REI(yZo!fGQb@+Qh=Jr!D z6q|>Wa$;~{9?lu~OqMbmdEXhX*HvJU@$w%#P!avfI|?Q0Zg1ZiSTjpvwsdyTyF@BZa* z2S90OD+1U{5^JJ#wB?c@ZN0zFt(0*ow68mY6x)N|JrTM#nvt!kL>1$v+uWgMt!boZ z$=HGmn5B@S=Zb5ElFytO{{3qVXg^2|`pr1ELdpS?(x$59Vu7{kH1DOMy^=pEa~b{U zh(g_p=9x`cv($Nh9ILeeKq50XO;u<%8}zn@FB#m4i-XQG?VEKa{%ZroqoY$Rod9Q3 zVk`!5-3oH)GTVV;g~Ji4Qh!sAI_30+0r4fsYBKcoZ*q#w0G?mUeMY~QifZDZ;f6-u z(vXwi=5XUWJnV->U9c7pGYrSrLWLu3?g+M#T*>7#xP_utw;?2#rB=e90fapq#{ab@FNQN8Qc%l#F(}8 zIrNC0)^>Fi7bdZ^T_Pp~oQ5*jlY@nv;bw(N6f;82003S~n)S}D*^RR z2DkP@U}Ko$fXUjdItrH~Be9#+dr{yl9{JIc`;2cy7Edg36e}+da9}^F3a%=a#oge( z@IYFwag;z%MZk{JbSH8k!nBf(Gwa~6U@nGqgm`@OatTg|m+{NB`$%?Xec+QBb)vPvALYi@3N;}a`_B6;@FSV3<@3xXv z=*t-gWb5Y;Ml~2muw^rspCdtRe8dB`ckIKSeSv8!HUrTQKikERCD}UDHL#zr$@F;x zMV*{*4a8G!=njUv1`dYl0bbn2T?3iK7OnxL)M*32^6?~cgZT!Y;#!qfP&6xD15f>G zuKrXqZY6t5j@TYuD9-?V?Q^|61A9lPj;V-y2Zj$1kR4Ewk3Uem`vI(Bs3GtkFlWRl z$3uTOr)Qwwl+I!Xoe__9fcd|22$Q3)^O@_*$mu(1%pOR$>tXglJC>{NJg{g~vDxch zIbGA$;o}_Rn7(Y3C%1}odffA|&W>7?MO+=Zf(z#(dC#wum9ll7qrh` zFnqSGaLe^&y!MDc@`rOIw{~-sa8oj;Jvq_AQck4V7bk))lEs{M&_m%;NMHr+3A)T_ z@8#BnFsB`KpCf}Vey1HxogQ-mM(B+81CKyu&_%yjL59~QJ{+0qRF4o_2h0nm5K2t1H6T7?D$ zKeI8!4CC^jloUmO|Mntu#K>j`=aD#Tbw_gYna%!P9yqeQBc{1g>tN7=)ytITnVj{X zJEM(3x^D%7BWM=`DX#Pa|40Trc0k$H40!DIj*x3zCSWuS|kM>!=0l3DhGQVWhLc>i2l4yp$J`U&_ zqcNT%;1L7XcLqFS#OG;I?q!6OX$u{0$9V)iCc|x7$)QclUg7XiKf%MkQOd3P0u?JtYGkF%<5OHk%fP&s|lN)j8xD@EEI&CauqE4Yfu}v#(t)Qv*i?AR_aI(ckC1eL>zA6Rvn07p7v;6sgKfzZq&;ZR+?h26x;BWubg$-$km!&>6XX%7Mp(={;r zMf=Yg@o%eqomOSr2v4R>fo9;MwqSEd)3F8njsr$QQ*c5^=<|h(eR5K03YLT>yLLI< zCN%XKQKS-@4E5U0)5~aC>m(EPp63#!CfLgyKgoXKxdW1#JgqK;rY`Um$xOXQ*tMCk#**s+vhnJyF#pHKDJSimpjbFxSjXe=Jg0mkhv zY%JYT!q}~G1B{-eDcF(wxWiv-mj&rFOdx;4u#|j-j>B=-WOy7#GZgz04fW~uuH*vn z&c-hsO-ySjhbxwu4=96pa=aPj^POxifD&m@jNeKQ1B$Hast;n@4)BTf#D1{8j00c- zof$`16T0HT9K3?Y$Y2ip!Eiaz;3c~Gn%C7oJ|Hz% zBX`JQ|715jq^H9LS0z;0PnH7`&@{42xQ!Un0}i8x7}C?1r?UtHI2qB6;4q{oU%`-` z{D_D2s1<^Z!oFcUkKukkAP46pc~B3YdXDY8N|bCs1PaY`DaUWj!iP(412nmO3qo1eVi9AmYxbgs*#H{ynXm~S0x%G`iA^!b;WI{fg zNcwe_#!uACnSng{@))D|yp7`J4a{Nw%!ADhTu4WShTq%-$LlY20O;NI#o1PhMGTN; za#KYH(8OKxk__E)+q-DBRIGdaV@RMRB4PGY8&?@s|(6$XccM_t)9kXLutyE=Evru zB+WV8wD_bs*E!j^1HJ9baf5t5<=qa3TXYYGt?=#D?qt&4M=T=^@_ncG8t2x20%XTb zy8HQ9c%oThkWW_3m_L<_txQE3bgl4h8stH@w?V$|nThT=^|xcK(4d>N47$m1gM8oP z?ZHS!F3}m8Ss%`6kRSK$Monrb!H6C{S*`8M@ianlcuJabf&`wz_#0DB0@y8-lgEP) zrJSHpa$u7FR_HSm<$}7&GZVQLx(O;{dy*)06V!_?XBuIDtmu^jUm&17>A|<7@Svrg z1Sfl2Whjn_-(aQi?T`KCgmz+u-xb~cQ%){xudKGFp*$m8R}vi`kM5In`FKpF&Q+iV zu2(J{hV_6*>sD?>hZ!`K=LyITO4^w3TLW(zN-O{>D4zwwx`=n+ZPBW9a%e*uw-;kM zIYmVRvFt$$i;Y$#Fg!wq7u~p3`&?F~anJu8cHMEWKpd4(39NP4odz;=yv*2dU4q`3 zkYi6g!WX4c3G_aYrO`Ea7SX1Z9JeX)nSb1*WUnmzfti%>)zHIkewCR+vM8b1hN`0r z9W--Wo?|FhIf8gGT2=>4cm6>A9!~(W6aCk=fR(^v2kntJW>QLqo0O76lajq+CP*JX z;wRD4@O9iQX;QK|J1j<+v;6|nMc?nk@(bEALd~Xx@pDm5wNNqS0lqMg^+u!8cSYJN z{k`Q(0RKOGZ|&?ItV*EoG%BGTj`XJ*srObXT^W@yFGr-J_3h>62j>hl$7qDtY1)|g zNDih^$@W2_NQ^tPEnri!ZF3l?S%0>v5>t&};bbT>Se4IS3@ov#Na3mrvno)y(Ry4X zPPh@eqbf{Th|1NYgz2Xg`7dHHdGYxVCbw1fAYm#$HT>VQW>%T7{l*C9NDicO6O zVP`~yus@*yVI}g{JHGeZ4Hsw+4`%3L@#tXUvKNp1>eB}XyA=!n9B3(txg(ZH2n$*N%B##LH9>9^B-Uo%LI7poXe zZh>mM3#9Fo8gamMo?YPB$oWcA81Q?gF;JGWSB)UxMF8Kb7~nS{&++r4LB zWB@R2D%F!SCQr%L{r=Ig2<{m9o*CHx#CU{+T}m(;Y&@=)*}H2TY{ zB%j8`TSM@vglMQH_P|p{&8kZ5Nz%QHVx-ev%lXZ?Y#B+M^k@DsT;QoJ6$$ zChfqyZjy9Ob1cP!>eg6R>#A;OV?0o-aqOQ@SvuKAS_ z_BHVOV@Rj3)jplfSN-#o@jc0tCQHrFMU_hFk+y_u*hM#(_$`EJm;yGx-5(TnK zO(sYwGC})zs9=@TS1@8o{fRH5Wmw9T%u*e4UG;z~0oP|WnXxiZZz|)wdVoIiOeQC7 zAw^{hoTU;=-Jqv}`u+TlIR*omWt%(CN`60-eQ=cbsQ67rXH9NDH2||7q@%0{Bj#hN zT+fGdj)*_=hoOCl!r^83KDq%t)O@=`IM8TFqIsk-uQfBCOPVCFQcL^ zwS@?mNvL>OwygXibHu0o(#4a=y)Jn19P(gzzWD6I_)E^36bd!e-96g=XVs(9iKDyt!1Ii7Bj{fN1(dd1Ct|s+v=I)i(#Je14B=$ z2jl-;Tage=wMVy5*N}Pplz%p@l1QKa-9!iyUB{x?w=P+1!rwe9tQ4TJKcUm9q0`uD zlF929QT z@*@@{cTM)H7LyBOBb4P|J7HZ6(z%S!)FW)NoijUg>;&jIlJRDq& zmo=P&k9EI^9#4f1`W;gT+XsLYHTZj$ERfn-l)h7*g}5UjMvj5%c=^ zmFB*^V9?w2D1olh8TH(GrI@?wA&L{F=YqBmNm77Qxs&m$9v3a#CD$dW3w zb_1BWUvbXajLke3(TQ^E>gYAiApV1)N%ul)w{i(XU*~B5@AsBo$iw(L4cEXbpR&kAF_xy_6cQr?$63hHPB`_tjttPTA6Am18HaWg35hpnQCD8S;m+SG~(u7<`YUc(9*Ofw{|R5 zaGt7d$Db&yp)yg;N|ZMp4_p|is8VTkLZTXIT9TNkmb53Tog7b8gFNBa5fjytlNG%E zxxbu9R0E5mJrmWe!8LD6>yYl`!eMO;$I&f|A?W=%HaOA-dOgp8ax?5WesQw|LuH~` zxrJg@BT)@3v(Q#1s+F5Xn=13vg7h@CU@%J!k293MGD{5%v%6Q%QnTft?S?EhG}~7# z1VQwRt0S`1a`-$;?c}JgQ*fB2X45S*n^lSe233*ONN0JGjYXx#Fu*!GY(ti+!zQ+D z5imvdGPp(cW-GJQY{0ADSk+nDPE_X_!=YoqcH8~92J1;`V%Zr-cQrVVY(aO|hLlQc zJ6Of+Yb2@Jm@XPck)&1}=*!R1A{#&)v>1x#IeyR;^iY|jR?5(6v||g7?8;ilK46a8 zXQ$9?WXJAcUkD3t9wCc30(cp(JHrtG$t%Ed1n@4a>RX8;U^=8(O#N)CQn~Z=s;X@kzfk&e zA&!6zr~WP+0sPmD2|9T}-_Q0#6ea{eQ=2XY=QxglJz@X{wdg#0{^J*m=1?6ajsWM3 zf_%wF`@K|)A|CVZ!i?#*yQSG1YGNgh057BwmjwsUFSp+bpu1T9rAImjX~z*bIjfIj z{0&FI{%w?79Y?_a#9ws#nEMF`BVZpdGE3+iMeEs61gvIVB?f*aia?AVK?Kas@VqaB z&3e$bo`%t~)fk9?Xxl=)>wzh`s`Bq!LX_38M!7AWg%DD&f3@Od`hK{zE$rKmy81pF zIYIp}uA2n>$W&mZ4r_}enz4#`ND)VXhgjO%Kj)qf!#2K}(ZP`(RQFdi!o6^6Ms^LH zno)henh~s0r)GpoR;Omv${)=LvHPcH1Q*|_8MXG+jDDP#W(2>~OEYTys~H6+G$TIr zPiRJX=}R+e?W-BN1JE@iWEq^AQ7a$K2vxgYnh~b;)Qo^n_tK2)E;>{r6Dm&KC>c?P zIykCBZH3Sd^#~W~TRp*84Nj>^3 zNiNg&QIA@Ct4BzaIn|?9zv>a(=`Z!DmA86?uD#WxcE8mlGYtNd>Je7?TRpN3eyB$n z##24Q#`&s82yCqFA2$O2-#^I-;oX9P=KtilXU5PiBx0~aZZPs8H_H9x#6v|U$rmG9 zcRuo$;Jr@$7VGJy?@PGr}kYT*i(o+cIb_L_#e~A8Y z8iSJK;i!fxaqkzW!>V-!%dp~bYBYwZT97SID2YVy?UKmvr(zgP}K}P{&a!ywog7x}S z|0gF_U%cqUVfR?Tcyz`U$o6U>4rNY$)R@3gXMQ>Bd)6+^A$7E*-GlLVSemwV=ljcmIQOR{|Mu(*w7dK}*oK=`Jkx)@90m-pJQnaC*FJo-7WIMi)^XAeRSTqJ6>?OJVf!Qgn*#Ku^kP9zt!gV<~Dz zPjU4*tUtDApAq(F$rFzv<6Yu-RL@u;9!cjEIhHk}JdRh$C>S5{Fpq2p<2<6B9@!CH z$urkG>t`-UBadw#EkL1MlviErfT_-EGO@FLq5rOikP%_T$ zyV_*$*yE~2b>E-xu;esc8&<+9Sk)}z)o{m1m*0T}S`xbn37~0)WZY%F&8d3D*w?#8 z06gac94pNjzi_KPRQ_f(jX%YsZYsRtMONNYjHdCrvWQmD&FZK&35yis6vysXK&3;7v)99%xOmirXS7@yA8#b8N(F`AL zZ{~YGY~IQYF%)?Nn4=kaDzI}@_$W)2o2MJizvqZ|iBZ6ZLS@#{EZ<0RgXkNjP>+B` z_vgW>MJ_tfnj$m5A*7bYCyP!GV7dKd6q^E^@rLA9jcy9dq*G6viw$R;+jt1k_?<7c z%>vDJ^0SJg)d_>3imuRHn2BuSCckTCSBs#nME`5OHBmNxp&$f&WDL$5QrOK6W2qW8 zfQ$C^(nd~{)cc&c+sm#72e5|IN_gWXC8G}PJ54UX&Gj=B8^QW)0MnB@5krQCkMX4W z3Vl0c#kGu+f$@A4qycTnhFpHV$s&{Yj{E~#8TXdRNw;Xyxwo7%SfY?`Pypd7`H>}k zJzGIZI2x&%hS7@%Cs$98pZG|qETRup)J=@BRB1(y^%W9O&WA&+Bq#2O*lKB1t%n7k z>Nq$>=1~|!YgNr2DI}ojlyqY#I0WgHF|?ZFPLk12&Zyywabsu=UyT1^GC9<(p3F-q~G59hFk);hxT0c)tW_TdVEH5Byn z6;ZvzOf!)+6pSOufP>g_NpM(s-sUfso12z2!{hk~<)j3KD_2>;VRZt47~5W29k zUN^j0r5-;Skqcyno|_8ARxK7z#YRz< z<+_6r<+_8Ta@}}E$>{UiRP;Es7krk5)?^JuLr--aU3o{qqwiez{)?$;_j=uJMxu@` z4pc*wUy;-3%S{ElKMRtJ)NO51vHQoN+@^}%_%P)*E7;vs`XlV2HcPkb=achv*h8&v z^8Kk^HyomMAHS&K;bFb*RwLC*0uV+peU~6ZY@dN72`8;*z_&}&>(E=6gjW+rY^j_p9 z^?t@LG!9akkD>Bvy)l@o)*FMZYQ3MC=IjfHGYn73Q6XI9fKEk3;@Cu9|7myvBTR^+ zSXUyLMqPWy%~iwII)73BBg>Q(>our~`T9hWKgioqB6EdpET7aBankaXuTZhx_(&A% z^;W1>Z#2@2^+rG9P>i0EqmejXp`pr8*jQdiD}1oMmG9}WDH=`pw1c35Llcb-hEXF~ z8^%=7ydoR~tpb5)NrVIl8W^69Yif4TF@nJ{5H!Al4F?9AeM8;{ibrUKY~;8A*vPy* zqZf{WR^h<)+mSyR%AvH(TaX^;BPG@9NhzjmETdhNHVm|6WG`mO(!fB=R}cd&KjIi@ zYK3GioKbA&G1~9>c+ZjR{&>xOAO_&WoHK%;fnKk$;EMWVw~P6E8DlH36Zl|BXYZU- z;|aPlRM;@T5^FZvp{w58>Bo%sBo88B`JKYTC~~X&8*atv>j^Ig}NHLdwJ9O0^$uHl8{T-OLXbd4DiqM=#9 zxxD`uBQEH8&0h`_jZ2)zE!~riC+3JWjff(M8+(3kYzrc2S5A$)tmDRpGkMk54-914 zHI3<+GMfwNUz@l)qb_V7hf~91DjKry{Y~ z9M=kA1H%l25yJ+KnYR?g2HI-SS`3?0ieS6`O-_u=o?i()I6gD}%!_`VDqA(A{|6@? zD$rTkD|32+y5&rMNF@S?h zEDI_R5Cpu)eBR>JN3qX>K>*F%{C%|Teq?OLk_E$TV?sgzJryCLL3~uI!m$wQ*jZ}pj5+!BLvl?BuN1;X0ZsbY! zC*@l(Jn+Y=b_X4$l!L>%-M5DzO2D;`2po$Qf6xjgSXkZeTSbW_zGgpM!UczgH{&NO zl0eXn9ZpQu?MC^W-9x|y?d}T`aFdbU*x^sIy4~>#)$NXtMBQ#{M9Q~n> zKyk%jH`8?uMu54ojGk4mX~!?tf8^VS=NhD9L*%$_IF1!P0>^bLE>_WTowPfylaqb5 z8gtiQ)^VZbV}CjBxCVzlEIF>x-l1hL*8q7(si9HfUfKB8sWN`3RFnfbpryNVzO1JIXGUZ8Cf)n zim^h?Fi?5`5vu$mEPkIUmaUa=-JrOu$#&_=ZY>gD9Usv16V8Drxa&A3gPrSC<0WwSadXW%6`^WntVo**7*y|tOP@zA}i4BWYlL)hLvZIac5d3Xl$%2(<*J}EdIpGGh3lnd^#Ja+YTf z2Fo+!D3Gbr$}`s;;B(5XwE5~TTyjf~6Iv6y=?IDESe&>=py$rSO50n~2_+xK$)p0! zO(+OTto$5ys)P#PTx*BHAUJt>bPwkI%ZW;;@swAF42D-iJs2i(G{M@-U;tXUmr)57 z1)%PfB~kFHq=BHd=aX2O?aET9{Trm6k0mEHX*5HgM=OI$_}9}|09M=X`PfD)AcKe8 zj06h*c?mu7!q>(-0WKLazW&UYaex)J{?0J^%^b=Qy(dnL=xG1uDIM<#&E6ji6G1TC zLXgim2k)8ZQ+Ul*?p(fSXNHR!G~X@!y$3e=d0HBNLW!&-@gnEsi~On6{DHFP&pa(D z$%zN&7nmtIvABUHxQ+>PIv**Y6vpKEGvo2x<2UD=uXyM$$L$5qxsaQk@&rB4MFdydO=$~3{OPx1t(C9A^a`Ico4xC zlxH^RCb2p(L*gLrs~^KXrlB45c#fOXGF=WwILDMpZ~l`htx^}^T7i7v)y@;!4n7mjy@m|Z z?q_+hi1-Y54ddm5llx{X%ZK1XohkEqM<&)-j$S|ytDBO4Ti6x9IjYsF``?A8G1aS-bWQWcy<`()E$J(D_FuBXF>Pc zFe=g>>9<}lUiHZ1C*?mLs9jiL{&`NPn3aN;6l@ileqCWbGX>y*aRT-HdvKz5Eg$;F zBk=5{|3v(qebvPzRVXj?%P0U3&9-PdogUV+dz`092qKeo=DR&OlOdH}iE0v5=*h2Pm4?;c1PBPd&vDdpHE53?$)D#EiI={2e% zl$1$e=lMUY@ZN8I*Q$PJ;$!WoG~UA*qas|zQb(0rhDvfM9A1x!gQf9x30NKLGLK)Z z$X}a4RY$FC_q3{JAwZTqY5`^7P+eO|Bm=d}f_6DToYQ-V@9+lu%ZVZn!TGhi_N>Vb zNfDZ>HQvW;ux(RzQBCfTZpH1491ssqSFMNAy>0%Jt~fW9dnnDfhYuNLNfo&&4w5re za`0g2TnJ9~)p6bba=iCfvb_5x>P%pxf^Htas@|6@bYZ=}y^xCc?N&fobRSiEp5*j9 zC)aw9(n%}2&(nBCOvejzkcZXxC|>1Ydv(9Un*y#^ zt1e$726*-I&v&ca_J@H_mL5jm1}7ZDsC>T;=kdYG=-YfzRtjn1$7A#@IIcihRa{sG z@0r6^yUOV0xYbUW4Iejb?v+04O z;;Q@nnQkrt&rOLs#KBQ%Tr-|#;&rbp)Av|i7k!~F!TP%}$JX?upeLTQ?|ti$K;nI4 z$p|KY_v(qG`Hd!l&0FDp@m4yWlVqF>!p&ExlyS}MR24gpW~iR>i3kY`O7s*=I*GMop) z%5cJKBh}K%aGE1%oYr8lJQ5jYI8jGTk;hhsGs;(mq#W(aI-J3ZvpF71PE%l>3UR`Y zF1!va#M!C`M@At|D+T3>F{&z+K&-byoI!gb&R|3#PP3A|hV1^7W66j9aL)L#-jxX# zu|D9~;40P!bWv8N-Ev(8&Lh|doHN#^qgJ4u3Ot@cAE7_!(0-La`R~DrP#>_~6;SXV z)>0z}^jHz;1Jc}F5$V&d@L|tLpWcbKpg&a@71*xM9~A@VSH z2KscMtGzFu{)+Is%#hf}^F8T;<3+&t`=lHDd(w^lKIz7KpLAn>Pr5PQC*2tDlWz3y zNjLiYq#ONx(gmFr*Zb$Bi&uEj_WnKTq8VN}etb^4F~28W^wn#}@ApX;qm>`opOY>I z+}rv5J?Wx*ZoC8MB{+=!797x*d8Uhk6DX9nOAv^E3Jzm^1qbwIA4&^KET!CVSzAAX z10cLk!C~HS!6E3ZxqZ7RA;BSDA;BR&5`sgtgOOM##GJU`keoQ2QQ9m4f`@gsU>QVm`hp@fq6?bhvYM%s$ZeBH|mXNVNk-Zm-(7$Wpo*Wv5`~FtL zHO^5E7xb$hZr|U6xZs=>ar=Ik#6=DEqb9ByCic51Zr|^!xS-!#OS3rc#qYW&o(7TqE{|(1KkDOxeiz8aaI7OXwVTyHldt;z*2o3t zERx&zt4i*5Fx*m{99oL|0Wgi%y({IS38S|(6Wf#>(Sw+>cd=ab`vg(LP#xuRFJrt5 z=Gu5K-awqhcg5WAf@Fo+Z_Qle97S{O!$;LzH0)hA7aRqy%9ys_g>%uncjer^-=%Xg z9!Krm%SOyI-DxPcF+8g0fpEaGupNEEDhO z3ZrbALml-D%d~b$rY+fOon)7F>|k)pJVDs>9{|xQX%2yz7%Q);;mtG*RW=I9IADa2 z8RCA>9-4V_5Sy73V6mB(F}!k>u9K%LM1rxIpov4o*i7q*JpZ~)&{=A+7MyA0S0(o_ zHZzBhv6-NYbQ#8GV#FD9Z?Tz|G2E46d-)DOO7D8W!v`w=zEpN-+i5{`h|7aBxh^6+ zooKg(Vv{P&G((P@%n$)0F>Md za(@7T?p?GuI8oO(o;pI|f*DbD@n9%P$?>SZeCo&q9A$>3fT2>!i8dfr;^h`fj+{W( z!D6%&=qcbpd+nE$`4673z8 z!K#Hzsm8S!|4l~3nR@->H{)W(M8)pfmG`CN3IAvYs`lZ(n1+Pcm6t6>SM~|gvWk(G z)BrfL=@sr_7Wz%+fHk96bE)4DjB8NCMLf~n!Z+jDMlZL&yRv-qzwz;#;D6qO+ zmW#5=^>%RipCzmFVTfeBTljiAgcy(GpO-4EPR12|^T#)R;r{v<8GYYJPObBC z9Sn^W`R_3kwB$jRWLldTzYlOGiorT}<#`vXBLAZ3`%+;zLJ;ud9@8ia{#i;r`xc$`X-upH~^I7-aT42Slf4gRS4+s{?G6VL& zPoSH(U<_}%S*-9o%a|js_d%Wb?kk~Mv3eTyZ$Gkp-u3rsaW5Aa(vKe0f^Nn@{|ko2 zbzmX>q=%vOU}Qn!=T7?YUBT(5kjLZAgphcBl|Z2Hb076+9d;Sa-`;%E_`SL(hIbxe zL%&_-qjMMO3L3o2s+j)7db``eW$&+t<@wNl46mGp^n)u;`E3d#Djz~Hcuaq~$UKv!%7HY#{%YONqm_{B1Y z`8nv=fzE(+@X5y?OS|#6W7Z!{GroS=LjHABZS$3@w&y~&+7=}k=(lMEzv1GvP{2Zn zgB?IqiFqaxa2^&P2TJH?Q(&L0Mf1q*_MwA8W6+YX>13GLchfkuEO`IoV*B;wM{{Mm zX=ov8ovQ_wA(+JAVYzAbS;|-y`S115g5dn*5Bh)fcL-EVP))bnggn^%rb!6J6>R00 zLFhtvOgp*VAQTL@2gMUSYalgPZDfASiuvB0{C}-2KXKxr8E7%`NDB)cxksL#ix;E+ zwa{yHoBij)AbLNjBu!rGH?2QYc#F3gf0A_5PcR0{P~d6A2;Q{*;O&izl7pnAvzwNm zRcVX6DIKjw8Rd>}^F{Op|GQlGw^@BKU?$MrZu7Ar9mcR>^A%(kpX;B`60=pON;0<_ ze86Zt;9&1rvaj7V_bgQ(z1`Zg2rOW?2aG*dg@6(Zq|QYU)Y~mR2+@aP;(xBpN4AF_ zTd-E_^ma23hRY^{R-Pqz@0&)R#bnnzO+0QEyIp$-hKq2UHKza29zACCWuL$h?jfOy zTEkX7@Y|IJgn~~_0WRBpbpO|G1R!%qy2`Nq_SO%WJabcD*h;RsJz{Pv`{t&yaBU^* z+*B40lDa}Lymw1$`AdgSIi7GWAsA8;wkp`(R1wfb_~mYD2-{#SF0M$sueRXSZ)ym? z5ipak7Vs7v$L;z7Y=L9tiZ^%>z8~trmZ*QbcJO1E2kNG75YtQ5;643JLVQ@dt$5Ih z=;*Kmi~MCMR)P;)AKDIt{vu&cepJx*W~9lZVyd^tnV=kl6_~lrg$9#_YL4R&eUy0? zXKJ*CjH7X!(d)nYjL$K?xpUHCRVpMAVDk8J=iw!f6#!z3aEojeJG>cP5~PAlg4Sqk z<|n_%>s(L%`!AzpwjrgN8ngO;Y7FPu%DF*4xk+0|mD>fMDCc&2GRUoRZjdVqUuwnL zyq3W2sAA6(6AgwT(sTD2rr4wS-CK}i&!U<3CZgE0{NRAl>Xf62IUSDVM^$uknw$~q zPLF(PZN$etEZT`^-mI|`J<>cjB6`H4IIqXv(=A$PBX4@e zYp!V18k{ckpJB5&%>a)7nsWJDjY&_76%dU|i~RLZf4V?IcHCaL3_&=H63FkFABBaz zD-D^VnzTx%;XPbVus=AEScY@LK}H0fzF zk!Vxf?B2&QI|xY+Ke8DH(X?%1!L)Wl75?2&@Kzq`u-bng)4GyL&Z1ZE<6haW&9!Ni!ngj9bw$%Vn%ATCUWg-KD0%*cOW&_Bw zw!H}wA+qx@!4F4jxSEZco0O6_2q+uo?d?G|tE_kZ8;oj!Kf_9vZ>rDu2xjM&#{&)g z{pLqwqJIs9maui%4uY?3uQ=ccS9h|SHwxXFA*QNR8pb9~qM^D@zJ6U+%=K;1Sob=tY@s@()~ng$7D zPV(O>5#<<8BBMJ52KYn!@aX3uxQKSxCM3Ks8(e7d6N=jeWk!x=4z}%hO}`EylxrDU z(*+V<{zW}}vL^9zEq$_#kN>sVx*a&h(mFvp=%Nz)oN$ zR_wzvWJMvTS*d3c(PguYk3xC2m-$h(#H~6&NXajzhL~w#nnh!~rytt#%)fR^ZR4Y+ zD0hFcY5FYv?B8v6XL_4Nd4UtBS(C?~w2D1fyxD8|_;+db_cl?X@D?979P5u_+P$}W zRAUh@)=05ZkdLC;bdE6(dMtxx`lWeBNG{rZ5%~36&WnjaD6!b=a~wuyQY{t+Y`tYv zT*0z7j06kr&OjhQg1bX-2pZfa1b26L2@(<@xVyW%ySuv&?(*#<=iYO_wca00P1mk| zs_LnN*{tc^?U#Y%2hNlab_)>bux%Kwqy0qwvRJZ7MNMc&lq+@ZW076%h&u#H=kKIZ z4D;sLXDz3;86$d12UbF$mf({I`Yf{)#0F1~mz1eK`i;#OtG+i#E#A&! z*rsY~k|i%ZM7)$n5N>#WvF$YdpykV)(Y4TxOc`A7*{;4xZFEoe^hhOtc!hOR3`n*o z43|&Mtp{Yt1@f*?y*vk3#zobAkTHn@!5tDmWVIC*DJ6PPXoXzbejToj+Sn{O5dwr7P zQHt0P=429h8@Cxu&7}Eq&?orSORnSX6&kr1HZ&**Ho>AQ`2qKp33&n$YL`slT`+3uCbMENlvtgqESS5dMOoK)<1;nqy9VOCWwP^lm+kY*=|Z(%8H?+rh$o|FwYSt=$LuWaMAf3Z%+3Vy`S8&}1H zCjA48p;Et-jAWCc%tqU#xnN&>DZ@{5=>q{``kpY;U!T&|>sP;G<$YhS?fbAqt~OGP zxy*^vBz6uHL|7J8%jc&^G&Ml$Gewd=OGv{RIp>I(o3tCci-)+{JfCbx|7+8-rnKvM zwlT>`DZ8N}&hyDrjX6qk%7&`?z%7r%Dv{if#SD|8d%NUEx2)T6E?&H8O;B@g3}v#> zm+R6K$fR1b#^;u$ZsbEeq`B(ZqaTZI0?#;0{%n>r)}_fzcExyw2$PLn5+mXM$lZQ2 zSTfP!Vw6kKnOM2_)uN`GhG#R@h?C6`umqhqcrrBaKy0WzoJBbke;$=vhI@4 z{uS$$wH2>nT({?<8 z@Z6Iu;%C*w{p00loWPWG_-|)zFA`xWZh>pY@R2mODP$o9O4`P=E}F?pB|m1HYPa~0 zq%+PDz)1Zg*uLd?@*gcM^0XxrH(RA$hY?E{pmQ$>M~qcD7Ts@NMQ`NsuUfOa*$V5_ zIqF%yMm)s`Z(`8Ar|0|GwHpy#|7R%Vx5qMC7qWrjif_ciy>H0$@YUy-kzP^)4mn&@ z;y_Oi?(=7c^5`2frj>%|sUFUrr1NHJ9a*WlP!5lSvw}D#{-q)Ob9kxW&l#7o!b{*T zN#sF%RC9Zu?*1xfxJ9L5q~Rhk6g12lC~l`6tp+_-k35ydJh;EOlxpJqB*Fq6*b5Jv z4R4i(-_{e~$n@BnVAMLA{jGx+E{(&tIkFb+Pr%Zd_&_#1gt6)fIm6F(wwg-Sy`F0w zA>)@Wl)I+PvwdYYG`-*8#A{%&IVdJ?wb2CN+E#MGgwAQnS^VyYj4{bu zs@90TGp{bKFP%8ANz@6klqdKhC0Z+V2=MpN1?p4f@3_tE3&=RCXmO}7r{H_bY*SUt zC#X-n*W%1tkn08|iX@weYYU6}GK<*16n$FMmmV)9eXf2hcA6_n-IV;^xZ7|C35wmf z*%UFb0~h%PIhsy*lg-!vDS9M5>nk4cc$ru@Cy}dq$wn?FBmHBv)gt1%r3w#Kg6or3?D76{5k`1z3k*54GW* zSAGN7k}mtEsfcG$Ho7CaxUQdNQ3*=#%C_uoK-;NF;BZR3hW%@a0AiRvuyGC_xM%9S z7)3~>NmH}hB-=#8rt_s?3dg{FJWG{F-mwwywP=gEKJQ==-LTRYVo9E?Yx=%^~ zCvhDFc1WH3LH*vGn=of7BCbYolXgR8=DDgT*b(U4N{Y!6t&lJ1x}W>)dTeQbqGTy| z?Kova*K25BGBiw5c9D0tgaAvZ%W#_eZArwb?hvkzC$F#NNZB|5^*jG5d0%ZH%pTtv zJMNehvOga6sD%FI#_UU1f9y88%1FEc&5zHw!pl7}38>Kya_#oow6gh!`v?N_oum@@^rTVS8zlCAUd-uJpITB>__ zT!rz8H~3C89nJ!*Hf|T~9eSmoMAMh}_#;fW^p!uD?zZ7gE&+>U%{$3afbWc5^GUyN zT3((PWxgv1*Gm@8h%{Y{4NWq!+XFW_O=>-y=(@doC9-6GD5mg4v$esqaB{??T}b|M zTxe~^yFM)?69%Tbni(9Q0vaNqy?-s|n9yE1TiQp<=WgPCnv3~>?rk0CT=7d#9vOVc z8CGQW>wvnHW2=!v5#H|#3yR4FDLtFHS%^m{{nJMk@t#?0Sn+(E#}TNe#qAlt=RMoE z=~XrfeW>vHRlkMh!6%d9gB%7H_%%KqLph@JA!DDnHWA|k3Z4C!WJJVJAo~}K1vpK{ zQ)ONepnKa*;7NZum`}xAU`jZH`L#5=HKW>`r5fg?-<1b870_aV=(&{ilHZu-k=nys z5RQ9eUDFov>aCa)snR^sJUB}!=s%3CfmI3uCmn8op{j+P#qgB0_nI+5R;*S@P8Zi@Jk|O0jZ=-kIc(nY{>9Fj+i`wp|ihaFh({;%w z`b0@wDtgBw7`|ku0#)G>b+0^CXb8lLx5Cvv+|f<=ttG3Ma7phrElHxn3SE)D3f0fG z^)fmpR$!>osl<0#5G9Z)q{{8FLTESgX}WA_ay?dh?16I+L@Gqh#^wpydYumA$}N>? zKG&r@aRi%DBfZblb3s>n9jJbiPMXlKOZcT+5|*XrbvFBMW+KedfO=#7vP9Whb*lPW z>2T~7r7hiQ(|Tp0f6}8nr7zPvo$IT*W!4peU*>{}i45=Ue}>NpzCEuXG~jQEkDh?p6_^YJr|CH73vCqKb0-^7j`P zzpUXTkN8unx8TTUy$ohIb%QkDcEj^{iT-Y?-ts5PBW|lb)c-I(@KKA*v=7cT&FLx3 z&%v~>EN+6t$s_u@dP`LpQ-X(oAKmFG+vKASxAVj1;bT{SVZ9rL*=AT=O1j1~TiurG z4Ej%E(6-ycd5JRs(=Zts)nSrH3^JGl-eoWZ6N9dJPwo;%e>goQXfK`%R9QPc6&63q zhb5zP%byG@J$LmBZjsM{WIu}5U@Ay_{=#PBLjXt3sMYW`Tr|ZOlEybJT>{eD_0!GL zb2j%U-=ah_g+NTzPTcCu#ZKwf{m;+VkV;J_1$FNo*}|7o&&;W zDRS>Ly$6L4b)I}uZrL{dw=}*Wk<4V$^v0P@7>hNW)QMlq)4hdNMvXSg&ix8CIc!*3 zuZD_aZMsb0)2IEO-uA9lC*Ff0E|JKa!HT^uZ{o=*UsNx>KFE7zsjQX@ziI=iK=ho` z5wn$FIsUHElzpNan?~|&$yMfll2?1`kv=Hir7kLo|0K(duIc3-scED;A;Jh=(u$FH zTC}0VO^$xBBfRTKqjK4#@tt%c%OZs~g1u}bV|=xwu}k7CE9S14;y3vTg^t@rvXSqy zc}eymedvm$5`5;S6$OZIzfkBGe@vebO15a3UsafRueZRc<1M z=_njcHWyRr&)p}gAG?fMGyz<95KFuP4H@MqlJl7!OTwSPl1yCAFk;O!sd zE*M3Ga>re85-2x|{{c+fK>fAOQ+{j2HqV0bojyhCfUmqAI=&-)5d&QwtwidFs{y}ODdaSZl&Q;a#}Kv( z{0r#@oI!mr$yXb|y4u!=E-Fpzk`wv`v$&j1diCfFf{yo^&e8;M*huY^T6P1oCH~^! znBR59CBCc_^P_ps)riDfygzK;_)+5YP04w{Z(0T+-Xfab1(sZ^@=j=JPx3TkoF`cj z^?m>1vOwq!{|FStL0a)CNi>YHIA^pec2i@pvPK`}zS$G8psm(auG4yDbfCnR{nWNl z4RrQXU#=5{WXslDSqE#+%xr)3XUCw2aOtR-E^^IZ0YCV3lw@W$0*h1k$HMH@p0(j; zmsMX#cgG%@dU;^$p>F&~@$S9}4tgS`758^h5eoF5);Kc@GG1s+kQcad0Y}a5l=M?+ zmf#rO8PX`KDoBgr5^ljB+X;OflKqg>K-;j1OM}*vw&hWaUxSn`rKA3Mza{wAW>CfV z)6j><)eslovYrG#+gacFkPrm&UXVf@8OXBKUDPC8q;f z+P=-E0rF{Kd>4MBYtSEKI2&y2%FR$W#r?E-bD48Tr4D+s1%9seJfp2T99cNxEOZ0m|&Kpf9PS!s%T6y*1k8qFjoj4mV1QM4s{r+ z#UT7W$&ZKQBvxlZ`@1a3XkPBIS$* z1H)YRfbb1P>}S(0u;&OxX%@0_XmAwA4-D>P@_oNbC5M{n19WYtYlunSkzIDJxHya> zZ1@qwUDR6rTgy&iwxSYq4qP$?3x1=^V!R}QSVfDn`){y}O6Jq3o7qSjdtaMYq-+>f zl4E!0CO^31bA1pw%Jxx}9_Qqw+z`pMM5C=Oenq%ee|uQ*;v`%9&h^9SK<{L?muzj)=Z4V% z9CRpK`I`;6Knf3IdA^r2CX-&6GxmIDt;2gd%(3MksMtj6zcbA1R`C=vZ*Hw$AHY9! zcwTj~N$G^mVOM1HWRL2Uo&1#oYeF%?o$PMbvN^Fg(-u9d-w8!DS8v9b%jSqc*^I>&zq6WKO|9w&33{WlaUPz z_xWR*EF+40({{kgl#c>+*k*aJsN0}n3HZ4mrEWBbX*xH<=>bwjw)+M-DkF*c>}&W^ z`$cBg&f_Xdt+1FY+_Asyo8vlL<97ZK)poC=F&aJ46?!@7Tj|0c{QTaOK4E2D8swuM z5#*zVI=!T|EA7IUF`sAHvudPy>)lpfsGB4%PT=wT?LTu^J+A-^UfC`A=GE`Q56y(Z znYR4JJ96z_4*lPSU3$>Vt#yZeJfn@%MDCe$ zlqK0BDc%zylrioE(m;y&D@tr<2*6CW>GaGf` zhw+5<89tDI_700-d52Bb<2I#IOc7;ky!_>A#G7%awJMm6{@&_)lDC_SR$33SXHd!# zT>zFO-q2*jr3-orPeGgK?tNqk=Qv9r?0aI29D(S9hY>WUNQh~@?%-P&xOCVq;;gSC z5N1oyNS|Qt-+qGGg8yU*pE3xc6kP@}y<2Mg>dpl%FW3qtztpM=nV-%A!S-PRQ*@6} z01RLR;7)@=e`B27wzoz>OxF(K`z$)$ze(2sGecl+qx*z)xWJ@;J&uG>LVCvcdHaCv zvsgT9^d`Ocm>J@lQaeoN?99u_ZYvodkBco8BU8Jiqsg*;UcULL9*bM>Z_qgnoryW zH-Vi-?q$Jr^zG>Dsww1UUegxt!xEd*i(}+*&Ma^vG8$^ky$dPGltQK^9bG>k7#_+o zuWRqZlcL<&E8;#vFLEX2&@WF$OoU17^<#o$(E=d~;jQM>Ig{m`bn1KlT;!Y6$mj)j zUjyiOEO_1h%`ZuEc+!+>&Z@x!)nIfs#UlLlz8Y@%+z}MBaCqdZ0;7T~Lk;T~QC#o5 zG7XpCc!Zaj+nC+|uN{5t*lcU6G7N@r&7|G(*G&7@GHX|~!_Cxc{ zOio_h<`&EDTBn_?Qz9v?y@<3|`Ci?5G7HOyQqZhfUF<9##y{7eXE<+A-YO&ywK`(N zF+xo3!7+Mz0T1m`a^8DD=8kdbJv>3=j^XM({28GF0{ZXh|0vsh4vwG`_yv3pBr(%G zQhg3Ck^hb^-$HT-}8$Y1^M=#4Z8i17|8_fdDGi9-jt0F>&F_)m?1 z(H|m#xov>>S20dA7}--vTLu9983D=% zs6WDO^3A_~0on#A>Ywfb(g7k#1GX7c1KR2uR$3RJ=o*)iFLxQJ1R#xMd=B8~0NNm> z#|%5KCbtj}gA=UV9l**Ex#Aqee<_c$wOPJ64vrqVd_M}115!w+n*9U8kN3w5K&Thw z)a6vC`0LQ!|DU1%(1w_BDuDJcS%&{*1}HB`Sf>4oVT(IYeC$ad=#@Ve2Kr!2&*5(w z|NbHXlz4pthQKexzvBK!1fwx#y?iC7I8wS7&DK+Nt2qq+6rRdU01YB;4^zy+rAryY}k6q=VMGlO!)8#YPXlAjD7W+992q^1qwPHT>< zZ8T)z&jx!QZkopVAOS|=FEzO6 zQ{6>ha&VWJjr)~sQe@&wD2q1ts(u^d`rcR=bniE2<4db1vaZE4Nwc%5O&i_QDo!|l z(v2IXOkjPpNn5DXUUV4PSRwz0r>e~;9SMq-fTZb8(B3ymV&;>w1Uli>&#%UM!y>t< z5v9#k8#7eVHJBvA}pN7By_-F_KQCRitcdT}@noIoSBb<`y~w z+w|QWQoiGSH+WO(P3vUB+&J3r?tWAvEb#$!UGJX~qc;~wng4Bhc6VoguG}wb=so?o zO2x;CGV9pb_$=ME;ZGP3Yz4pf=QtEgFOCVW?NzzR^}?)iU)LNG7`+}h=m_DuKnLNl zqYT7r+kgIVe%n2&*4cKtgma1k!QPphsD7V5K40rGEY=L%NU}>b?7+_lwnjojx_7UR zm&A$jHqG?1TPLrX}YT zqf8gs5l51`ncmNDUf&@T%eIW5zui}D86~|jx(Y|kyW)#XvUCz_>P4l1-8V|AZrPp* z8eq_mheiN}=~}YOY&9?TbCt4t3#lg2<>IR@Nq3c&va{yj)GspH&{&lYcqBJ3U>8Go$50kUAWfB#LeFnS<3I1FuB`&w9U1Dug zUNxVJ&}g2aCv?r;y{lm(?CGhW9qw1W1kaq&J!>S1AnuhonU>2}_m#2H)*JWK!2ZB+ zAo^uq4^bQ@IeHc*$%v@|U95*e>k!4-C>G$B8eq@+X2OVl;#&s=-s@zk=HyehBkbMf z$_3c&Nmf}}<2|h?FBJ|8eC}t;4a%N9W#$i(zog&!FMgD5=-Iqbd>^Z_4{r3a-Q#8d zB^~9 zMo8d1{nzbu+#=J=diERv&P>PydHS~ZrIuaoawE+WDt@$$?B%EKgKJM_BD2(2o zsX^s_I>_z|!n;C4%@|yYvh*$;ERMpz{vi(mA;o(5UVj}_N-!;TVXM%RE!rIZ`})Uv zPGW6-iOyuTN8^B<avppPfEp67+ z&l>fo4omf>dp-&Soc8o=1*DhfT`tqwLhbiZflTK1&^@T1@hZ{Odkr-G10`bI>&c3W zsZa>wKDz8rAq&T`j5ZQHGtkA%f3!UdWyO1-(ITx@yT-X7{i8QFZ>K!k?m(wF0*?&`<&)sI7wU@AHgZ^$_4{m`CDT=Cqpt_JF(fvM+d zRL%^31L+M&sz|M1=OBZK(>Ne!1|%tE-U6=6VZ)&yG-x@GnjZ+C2CytvnZlx_5dr;HxCcgu~fG-dqcp0QX#U~rZU0ylI`@)ZRzai)BM-Bpm zaV7}Xz|G%U9N(=B;UU53^=>)X9z7pGMuBm^M{7`3Gg*7S(G$CUf}4Hx(8`J}o7=zN zv$SBaUNt_mtH50Bs9=O%{eqM7Q9OULb60qYH4wJmk$LBCXv1tYG`$uzDb zf8mc*KN>y=s8hz*xf|-u4FaW-yFY9o#|RLglN!5i`bWq|t}|XSljO1R>ws79?wa+= z78)6gM1XZ!X)TumMMs?*7a0iY^pPt&I`?=Q8w(HPO`808zC^inm?&Q|Ti1eb_N#x2 zlNt}X!JkY-*DN`mpw}-Y#m7~P+bsT+H{@KA3&kwP;m|k5grZ2nz6`GAk#Z%%RNA&ku-M}7;!stIijqT0 zIWj*u%_>-bEAiP#%v8pq=ED~6JdKhkvZNNX@0o}>5w>MLyQwxNsCAE7HQ_vWvTa!<8Bdw3Xn&PETAX-B@j>rS;{rTWsiSSrD&cupPFF}N z0bTd3flxy=!Xzyp0G*g6c&7NSN5|oFqrrGCuLcuhSS%p!-@3sl9dv&-#!@A* zEZf|-g?JIFI}ROh18;4!aNOCPOh^$2JmA40&WQ6ksW>it51y2AXAQVDvPL6nVj-e| zj6UPmhKSRHKc9RFgY4hp56<`;Y>ue-3lfunR`M>-N1)7J{><`Iv2_Q8>3CkO9)dC& z2McL_p|9*{!3FUU-v)#fJahVRe|gP>7_S!02?*V)6K1c^QoSxk2J@ISi6rjCyUO~- ze-k|9`#UlYZA+~B&G*8VkvA+Lr(h}gAag|~RHR=uw<}1CzoU5wr+x=-y;rk$c%n6b zj;*L9)y=5hr!7@trYB79>?&C7Xk0dwY7fw$!hR1$=~L~plxz5rS)tC~zy~4yxO46~ zs!kDZ?pe1as8JXAJPq|}3TF{SO;@GUd&XR&*yl=lwcnBE)bjgI1a(5SfnlLX7>F#< z3?|l3%Eok~S~k(^w}Ip!kkGRq{0>`re3c0~weqQCM--Z_j1Wu}s3jxy(R2y#7Or`(cM6RM-R1VUi6b+`r$XTk zNVpo$w z!HD;w?s}&JhMxCaSw|IM&bnf+E+>{!{pk#zu=&~N{RnE3T{&P+=BRPB$64Bov<0zK z5q#$!7||KyaQhqcnRa0jM_TK_$VX~E@>e5)hP+1#91ZEPHSg6Gx5*v0|s0F)^ zSBKG~BHz=pl=9AUp`s=-O$Nf+G1Yeu29b5^)m&v{csAXJAVX?TnX4d_$2wbrtq?ZS znl|DGf{ns!!Q`ynLFQJs84H=-VUObxNZ_@$!e8=7=-(U}tF6VTgWwEC<|VSsF6XEj zHb#FGJAE<^walJy{b81tz|xjVhyVkTAk+?${v5d|`4JS&V~Uor;*5a=hh^Rn%bQ2- zq(F&pN_CI9DCwdLu1HG2a%$j7On5e8n6MJE2iu5o^?IM?PS9bJn)ChSw`0lRhHlzI zdkxI!Sd{UZ{6OOr8{3*xx^BX;aXv4V^{_c*ByYE=#?iW6FROTk87Hi7w&Io$pvAf}E%V4KgqKW}#zG^WSwpLnX6=o? zY5VrDKkL8A=fT(KgVXME)5+TtDs;eEjGMKOeqXXjO);)Jdl|MNcf{4tQ?i#2#{|5? z+bK`Y;_r@~z4V_xwE;eBn+;ZaIE(krflfxGiQ~fq<{kkisDPb+swrO|-p}e()v^9? zWvgFUlWsbe)Lm|o_|0-h4JWDPsgF;CZn77ae5(3B_}3n^U%Ysr>(8Aw^Hawbmjl0y z!4+(lpUKQzsw@0f8taoR+l#HnSBf4L;_5{SsWBW4b>o-tjQ3vjdd1W6Swf$^yph*b z_NO{V!W|@C#?qE<@wb-g6F`O0D`+Rob>nZkfHLL-mCuR+N^Qtw0R1V{#s=I}nmb|imqOW+G1=uYZ;<%*$CMlFXU86kZ)%E(*)g>R`(k3Iq{6+bVKV{cH;@aO(w}#BL#1tRWF4k2)r&KDT2iAva!b!# z`hwM|;BsIhbn*^;5%ngb53fOLl6+fr#bB1GL~XvCvBUfJs%4py^Oot%b4D|JecjVu zw-WcxY)unMPM4IM^p%gi0k~b|v|)=?>ZC$nn$v$ZQz$YrHxJ%W>qL+EyNHS)F&tdt z!722zyufu8E$$2IEj7Ye8lv#>3y8FS?Oz?+s%0NQT#n-xzFOvhSa^G%z(ti zbSip965a{!Su=UY>YMhI|Mj(0xcU>5yHmWfTtE=%O5eb=XEON;TvQ##BC3vD#~rm> zMWeZZNs4wh%7I(FDi+?5MnJNOGLgv_7I&?YQmNcv%`pt17CZ%48Z7LKzELLWnKJrIB zF(`PHpY9>`>}BlS%X<_bcEp5>uRrhZsz88~B4}u3$1ss;M?uhHG<9`;rTV%sJH8Fw zo3zE+INoic1!T0Ne!M+ht8ewZ6!8LjT*ZSTHk7&W)sjagvLD^ss%rhw`}zNT<{6Y{ zQ1?Cu^vn?4*JBuL<3oN^e%H^qo~2OlRmlkLe_X`~JrqPFz*v-+&MC#263`QR@6!3f zaLU+STrD|qBw+KK-~|?+23_r&ceTU#XsU_AkFT$WDKd-xoMXj%#hcoKx;}=4akjHI zxhs!pUUCJWf5-&c2|@MZX$m-6bA6HDTf;#*Y5k=oaE)nyxPC5p+gE%pgi_yCMe+8T=OWsj z^!$f``+n>5v;))doDXe1*xC$jE7z{Khyq`^{h^vuUrF4a=X)aLt3eyTZBoV=6&rsb z!;v>y6Vnq<-rY}sn0ZCTWge*tSA`t&TA=9J*}-)=Uo^pToKfw4+Bann)HhBoRUtx5%3w&N$x(u9Qan}j z)~=a62OComUo3x{9g&B26pP~3V$PP4ZTtD*ds}>jX>ziN?AG>*!%S^`-A0<%Z51=2e^q_!_3| zidTcLs`CB2-`5s1WwvHtxSDt^R%V0`S7>>fA)>3D3AEX?NU9W|rTc;g=^zNl>>FXU z#=WyOA$GZTaH39{74NT0>UiWEfNb5B#;m^r^dGk) z;A;48(<(k5f8#}qtaqg}@!<1tE>%G|c3RDoyVlypyBen*6?c_1gFMf>1;{ZhP0OE0 zGmi*!es3n+v@@1%7Q&j)?o*V@_9eAt4$_Z^!35I@_hTgPC#;z9-9ZtHO)6Y-EI)sH zXT>R$(c~K$rB8sRN(?nsB6pVFI$CAT)=qWC?z>wmDHTlqaJRgo^X%?^-y<<;De=c4VFo-_ZDso;%f8v#k{42q$?;&4X3^f_P_RY-Mclnu!eAmzp`cRhv}@e>jDHtm5z83uCk|)e|1U24fdiWrWZzP>x_*BdP|= z;V>XR7|^GC+;OX?6BJ_DOyK(uc=M|_qkn@JJf0JjO{CF4lQSrt7bmzLVR2Qnhasqc zbJ+mp%_%;LZCxm9TG~D$FH}5+ov&Ioym5|b24+_MB-VIgs$H&)elO^2V})f`>7u!R z^YD!<(NP-5h4Et!6z;dZ^joTAdz0vX(qwza62oqkT>6j~Mpnk#q){$mQM44-v(}F3 zvI!pJyqvumfjTvCCy%jl(FNUQS)JNh;;UOY3`4WMsYa@dW`OQ`O4a3}Iaw9iZUOIZ zp5Q9iywmDI%4Mtv3mHZ{=?R@x!Z_paj1c5sf!mroRwX1~aIAf&peXU3+JwLy+cSA$cROE(w)GKavf z^(u>oeA3%fa`S>m8JJk74!qpj4~2Bstd{V5yXtajc6tCfP8LQYW<)y<_xL9KH%b(- zQ#l+;Vidzo%iEAdvX1Tb<;W#Vay9Z z&=-|v%8Jk9AlrAIG7r5o#f5v`xKsRS0jbM9_NnCe>-chIK`fZvTJBKuu zFZH>VId<3SBZMXKHRZ~(=hI?1nI7VudF2!s#JHy_$C!MOA+D-0J8WNOb$r>4QC935 zW|twJPs#<>$jcBTzUQA6rKdc4Hxr%6&FL{QU2%3PYDn?tQu0b>0kr+xG!tP{E=sA_ z#I)eruUwR3ZACb`@Z89SCbPqA{G6DD_DKn%7L&j5Hq7C#Q=VGt%a6fZVh9{@b32V#_&qnX;-i;}h=_Y9QW{QS+ zdhi1wIt=9&Y&|O_ax>z}q_Ac&D4&% z8Dvt9sZ_)gYoc;dxZNhE&%ID(QW%wR@pI!vJJr}6hZjo=wr;ao9iPSd3NJo!7Y*xH}(ov1(+ z?{kWbwEVv+-ALJ28;9eri4RHh1e#pjwf27o6~D3!2>&820V-Pui@p7;?^#nEfbquu z6{>iiHz&8})?Zy4QA#Ud05}NFGNAeEz?1Xu96&WXQIYZ&>7UyrPVSqX{rSO(itkwl zpuds)!3SIp{WSzxjYd6h{GA3E@QC*T3H*uH&SBHQ?*I7sn^#IBkrB=7JYY5X`r#3X z8JO}n<`e*Mv3J506e;U*NJRW!r$ABy02<%`2o*2|VE%+^{~u(`G`l4uK;8Ox4v_U9 zBL2kzPzu2ON0pL=8C=NlUljnxBc5kTlu~Qwuo#>*DOdl> znqrTX1zb*iJ{#Jx4PlL&PQ}MzA zD9HaiiT~zxUO?~ouPQ)J0RKR$Tw`W;|EfI@szwwCa{r4GtBbq8+qc@m1-k6N3@`IO z0qScFRMp<$0mufZ`5GtpbJxVdzg8Kf{CCyX;UWI-Rb4J_AeCb02wog7x)NTuIOKlQTmT9UVh}f0 z4-{2=wR51{7+T@zumaqe;Y{me<}(zlzL+# zN}+XE#VQH$<E(4saM2;SX`w*l}$nWC|L3Vc&)$%o@w zGV-Pr6h`jjp4cz#jv%~c%3ZEDh#Y~AlZ<~LWE;Y}wRYSv%jX#&pDidfAAY}{PVk1c zeabQo_M3GAJlrXD4hQ1PIvY?GKb25HFC$x)+G?eUvf* z&^bptjU>p{Rtfa+FlV~X?SfGisXqRx{@OnNoI*a(Ps?ZV(l5k35etNz#BX4$i2_6W zi*KCj6Ay^)cNSw>(9;Nskvce{5myxyh`e+xgLrH?7SA}>Nrx$j$~%nV#c$`~b%V)K zB7AkEx*@HwjX5&ibx!%AQ*D{uvy52oa2fUca6`&Cm{4BaO7FR)LZHDKX)g`jn#F1@BOy8DGgd{XX_W^DK?WeyWJ`?6{JElaQhpN@) zvy&ReS2T1N7tt=dyZ-7Er*;_JK?2K^(1Uxf&joP0^Dd7LI6~@QQHaVm+x8Nmsa@8u zKMo82zTza33O7J$)Z*wQYVWvh#81u$)*;hJSr1Lp7~>62YqfU7upe)Tkr;&hVaCTV zI0%{iq{w3pUIjZ3ubi>Oq3GqgPHj zCc<^rpFZRN>K5tEcc<-=`2_XsCX)c&5U2SSg`1o(F|o{s9@BpOce$A)*I7`)%*%U( zkg9ZHlrw|W=>ydfm!W%H)F(h zj0rMkenbJkqoLV|DEkGkDoNq|KOn{Zgci}`(~}H(il+1xTzQ z-J+97L=M4zUDU9Z3w0RMW4#X5tdEGZt-o)d$Pi|q)Zz!q3$5$41h-H5#X*%dgxRiu z24SlQcg)_8*pne`C)wWmS)RIEeij?&t?+leG|NB39nciRCEyZV>R)dQ^pf(kEd;x!km_}*1i%;l8~!FMW@uq2SPwicr?X0Qpj1SN9c^o_HermFoiRap4X0O zA&@e~x8b}&QsM7bfY3un(b9b@{cB}fLC-8aQOcK7NDA^jRG)%g>Ol?K5xhx^X#>t# z^NGhg+!h)edA+#neX?M!7GeZRJZ3TZrH^+pOwa)Cu@t0w14eX8F*#y9jk-AB+Ply| zv#arF$Lv6%^oRs&om7foCtjAeGv|%5lp`NW{NEbzE`2%;Xyc6BU&+d&SWx&Bbj`>Z zxx!6|eO?La<{&SpYn6w5T+Lu??tU*`gn=%|u=Ycs!1r9`cB=G{S~~gWFg7A`D{)c~ zcgvQ$7lh_sFl5F*7{4BjDJ3YN zX;@TxG@57QVF#3=6<5fk2@S0m`L3lQxxjWAm4f|V<=WxA$FKu@F4vD&lUKIN{g9gd zb~l=|58*nlRw|C*a+Dd4%@CeR*ZDoF(4mADdUA&Ef8doz7A#q`Gvj0-Cr#MEx=7%R2&^TP`}E_2=>M;>>kMjY zTiY~44G@|XfdCSE4IM(S(u-20g^nPB1f+u?y@V=7s(=(hItVJgONoFA3Q8Ag(yKvl z@Z5XuneYDk*6dl?`&n;)-kHoKv)7`sVx~5c$zpg*d=3l^Ve{FI?XUOzmRZdjg%|r> zcbI-?TI0MZ=_Pk&sR3PEcVaB3p zN%8D{7p9xXJx2Ckmaop){S6-fSF49XRtVo;hl4+~gJaj7gR0_%&y8M>si$NXr*hjI z!qJ`{{6qtvEB8$Vf@nu}oQd`Evtk=;`B|=M|5U7r1EY?72a*MT^k>^8CPfVm{ zf-&oj@-D_FajKEj>&e3+ZE|hw;}X~N`NM2RrG)KwFU57zSpNc_g-6->{xx}uKHC_gG)*!r-M39zQi2{Q!3S2 z*pXSg+@@T4lK;1usqmPu>lJm5a9CrYv0O*5Se&CTXpANn$y+2R=e zNs`ZaZIjv8PMhi+vz@>Hs%WlkhG#9+(l+hq2W;p2)P0YggiHQk`nfa*HY?kx0AS=Be|yWpbX~kv7Q-;X9#g*E3l;V5=ShM7*apUtf;79f+^p(3w*E zs>Mva(0ck4^P9l=?dRs+{Cv1I)%j*x4(XrU0;{( zpq)}1><(pEXi4z|+_j0JyZ3t}V`XL#H3@jDLv*Q`rCPx0-HOe#7WP z^@~{|+u@scvCVtWPvMQmY?sQE zl|=hHGO4QzMU(_6jP{S%CJVy8``FidenvCoMp|Yl_KIr%!kt;c*_b@Ny{{S?Q3c<~x78s<6g^uW8; zky4&Hz#y(0pTDe=(nybX1o|%iG_8qGW=TwPG zoP=X^JZYMXS>JHw^!-(H{>>;Y_?TCj;#8Zg9W?{&CB`BxiC-dI2CAqf1JFBDxJGIvEDFrR8@habo?y_ioy(geq zx-_2kR#9(u`|P=q5Hi>~p~vwLv5qnF_sg7@E$Pz8;FTT6&w)G;A(!j$sicFbhQ0Oo zzWq-0-lQFUkM5@fBg8pq?|CR|`qZqx_KkMrUWoUpdHkk=>ccIzkhtnQ2L%F|)+2=1 zrtWbsv^=3`)Jds#N!6EonW!SgWIgK6y=$H6$Gs4XWPjvQvP(Quh62cMEqLA?@&EXE z=+-Zcdb*Xu@t5m;qs32H+NC6o@gtMhooH9eY*%SyTOWC41FPq1~Jh|suoUaG=hMwNGQQ$;5CgUF*|uuhCYEb=r8rvcn$1GgAj#$hJ6Gx zI9rmlhk#z$6*kKDGCYgqy^YG+9KA$PR1w~eaZkn`eSwowbWt}JPEfZ;-zUhRf)VSQ7AL&^J&7{hiLeJ*eRj{arbKZ4Id z{nV2_{8fk5cEXuy(x%#N%Od#hG)ZKd9y zN33SOHrqp)ZxtNY1k*lpOreqYDezzE+NUR3{#>Y{FSaJgSS@P{hlad8Uq(IaXb4-% zWeSU|@~#LWn7-lUuHuq!042{>o*ze&DwxxeCF=ilLYB-n$5|fadP@n-%D-7E@O?55 zJ-7fEFW(#4skmPLD7#;x&x=^GQ`U`z10lP~piDFOs%kEFja7bPj zFgDWAr+&71e{Q6_0Q%9tQzyxEdNZXWOmMWXO)8Y5c3xV_kQ);Jz9ft`FRLR+eVgw! z>{Ih(s^{l6Du?Oi)97Zr$H&zho|tGcXZMVwFaKu*&)u8qd-oA6)qs&Kw-&KZ~k>#bQX{)*g#3WiT^^pvXsB5Cxwc3m1e^L;HA|~3GgB#&WNqkBqr`P%W3$m!lpoYk z!Sjqfq{Xh>!En;3lKoy?2+POW4D`ACa64^UNV)i-0Z>?n>m$x-+v)oG}gLk;i z>kyhbZwh*97ROi-nzI=_syt5lLScGD$%knCUJPR5kkc*y0L!Dia2N84#i6Tv*tHN8)%g(OO^D*6+C2MK6JE%2ZfOk#MS z<`Hgj*SBeYT^P7M1`2(8V0;~Vxg~M`@R>`HF}DtyB)Oy}_M8HWR@gBz89ypHdwm0B zkhO#!{-9SU@Ic)Js$qr^WChjl=Ar>z#oX5!X5@z|aa#{E+#aAfQjFPl#?~l=Y0TQLHL>@Nm3{9Gz2hMQ==hk|TLwG(Ze^$L@g`)Z0 zk&4343ZhKI+7@J){6BBo1vI@~-c^P3ZwcGWTg{tv4f)7SEpWPTP~Z#SJ9AQNcv2fW z_acTnsAvc)VJ%zytW8~vJ;#>$+dW0%G~@Q7Wl2i1vp3z7^O^XG`$2|pt5+X(#>SST zv8wB2F(rzkO!6N+0F!~wxz2ye{19~xbz-Y-Oes=6@BfW*nc`uYZGb)oQlLfvG40NT zrSNe8px1x?NNz3*IR7~dNU{7r3z!tc&BFLC9hmPTEI%jB!U!%EVv%9FcO#@R zqKPA`;64U$l2PMnZ{7QsDDFsZmRWhvQGn)XI&j-2{|BSRU8(wo)VF|>xfUnF^~)N+ zisacG(U>A&!jk+0oga*2Nm*>L{=K{sZ~8th@>A9fi-N$Hag%*!s*a zBV^_Y22YXsW_t`?Cv9>jPZCk1_-*M|&ESW-vADCR@I!J;s%Ux#c};rGI=F%dhZ~La z+aE|!ES9Q};!!NFyppdj6&)%r!V)VyT(G`HTTQ01_uC5cvLtD-&kP9YrFGXNQet83 ziPhmLo=KK6b+A<5^AR^~R4oW88M=3GU&*F|fl4O3t4oK*1cWWm3DVqSwJ_s!6(Trb zv&MTECp-|hSpAIij8bOVfXE>5x9l}@-_kae5z&I7Rzsf5j75#KGuQjith?V`j@vcv zr)%xy#RB;s?h@wcvV}|DrEVmZ_Tc=1+OIh=ZX2L(f3G`vpzhO* zm%*fqKR>dZsl}*XlnQ@ z6ngCrXV@;)dq+?gLNf}XrtnrV9T@ow=^{!Z zFWco)w3C$|Dxac77FYu3QrmeYJDHil;A8YEt&zjL)@PVNWb=M@(LEAy9eDzEhxLa3 zYc)d9RA5c4LJ>VN=SejcMSk|t3nB?VmdG@^+j)sL$4%ppKUasfCI=`>NGqE+Y`U_T z(c_+h%k>`QV)fg#7nbXIw=V^?C1ynan}o7*S#~Ab#mvXZXww^@#O8 z!33%H;ed%@;tyzhq+zgxW)bV>qcI~p&qJSV&jcvJ!K$pZ&a>p04NANkZ;;u&Pm5A= zn!q#S?9>43@&>*s{HIt2F5YxDi}F`-t6P#^Erz6Rdq&FGg%k>*F1)pD>v0|Y>&=8b z^_vwzR6c+}SrNszBh`qb9g?Y{)-!FX)s8?VWauaAeUPM62+;`*JASikVv3AO)iT$F z|MRmru8`KVIMWb*MH(#cfR?@2J}CA=a33TZl65xhK-SC#^ouzC`&p%~Z&V*xf~ z;~zX<<`SJbi?i!vX77;{-cLLEwzUA#^9bqB)z%H^=hLHSk7ysz^47{e)Bb8dsC9zh zeMUkpuUmc%tjQ0eCAn$6H&)~Qo%$uQ3Ek-d|Dc4z6spxIA%YR{WQpKy%r}A~j;&b1 z>b$Nn@t+11T;s}gw5KSRbLMeW%1k9~$n&vx^N!=`L8rW97Du=4vKy>sq`8E{U?w~F z3*Vi(;ZyVc5}hHJ-mP_v-BMpiq~LY#b$vpZ7g!$MGyfQ<_hGbvL@Tm=RQ|O7Osh+5 zFZ|tmv?^%bcg@iRl=a1K_mL}?Svj;ciu;-<=Q|G@+4!>_7p;MYLl6YQU z0;XC5&R&$SM8__K@Ncf)j=BCje1N&z@JGVD2;g}sN3H(Q;Nt~jFE*QLPS1hbl`7Ux zO?NRFov0VRRmHpWc}RW|X_{$}G9JC{=}9uNZlW{9Cuizha7g`FFI0^s_n2CCs4Qw{ zzUVgpT*EEBZyco5^@aM0pvuzw9|=Dt=dJKEYrxR7P413jNQ5 z_`eqv6t31pGy;6JOnu?L&fvd*D-14*djN~5ser}6B93s}tf+V;jGJ-Sza2>aZ3ir3 z7U=B^7BPamI{&e01V=i1`Qp?>!6ttIeP<_kxGEw5d>03af+eM-gk{BLMWv;|ViID~ z!ccK>X-NsN%^z7cgqJT)8wG~`t*(Vc_<8>$zQ5f4L9bMBL&Ovp1QPCr^1gEI82BGp z2keXV`xDfakQz=(-T8sLqqB*Y>Xpb}X^EIPqY!>bM`slHsyHV9Nb)L$t14cV7aabZy~ubZ#0x10#B z&N$E&3HNq$cSPZSq;%!u`{Tw4iE#3BbpHRS{g))^f4KiEel@r++!NvYmuVbfsDIP+ zU!4G?vkRFhScXjW?*W#Ql!QuxUBG`ZTt7)l;wp1Bz+Qi2;*vOS|A}4Qi{Z-iPYkCk zF8;q5t^@u@PD%_{y#J#sErIKb|6smIxVxt_@(($t?m^DDkgr%aK_Gm=SBd}Cdpce& e2=EmRe|Sat!jZmzGQ>q9Eh|aJ&8=amN%k+yi%2E_ literal 0 HcmV?d00001 From 752da5ca3fe5f3b4cf4624df95cd69c49a23ae6b Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 9 Apr 2012 16:31:55 +0200 Subject: [PATCH 122/183] use pan_overlap_vertical only in pan_by_page mode --- unireader.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/unireader.lua b/unireader.lua index 5f4c99a97..ddebed7cd 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1195,7 +1195,7 @@ function UniReader:addAllCommands() y = unireader.shift_y / 5 elseif unireader.pan_by_page then x = G_width - y = G_height + y = G_height - unireader.pan_overlap_vertical -- overlap for lines which didn't fit else x = unireader.shift_x y = unireader.shift_y @@ -1234,14 +1234,14 @@ function UniReader:addAllCommands() unireader.offset_x = unireader.min_offset_x end elseif keydef.keycode == KEY_FW_UP then - unireader.offset_y = unireader.offset_y + y - unireader.pan_overlap_vertical + unireader.offset_y = unireader.offset_y + y if unireader.offset_y > 0 then unireader.offset_y = 0 elseif unireader.pan_by_page then unireader.show_overlap = unireader.pan_overlap_vertical -- bottom end elseif keydef.keycode == KEY_FW_DOWN then - unireader.offset_y = unireader.offset_y - y + unireader.pan_overlap_vertical + unireader.offset_y = unireader.offset_y - y if unireader.offset_y < unireader.min_offset_y then unireader.offset_y = unireader.min_offset_y elseif unireader.pan_by_page then From a375febf8ef711b2e62aa1fabce88c2732372a95 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 18:52:20 +0200 Subject: [PATCH 123/183] patch for muPDF to allow fonts from files --- mupdf.patch | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 mupdf.patch diff --git a/mupdf.patch b/mupdf.patch new file mode 100644 index 000000000..1eec8aa70 --- /dev/null +++ b/mupdf.patch @@ -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 From ec2eda0c0525017cb2538c87e2920b54a281caa8 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 19:04:26 +0200 Subject: [PATCH 124/183] switched font handling to only work with external fonts now you can use a environment variable, FONTDIR, to point to a font dir. Otherwise, "./fonts" is used by default. Subdirectories are indexed. --- Makefile | 6 ++-- cre.cpp | 27 ++++++---------- crereader.lua | 13 ++++++-- filechooser.lua | 22 ++++++------- filesearcher.lua | 24 +++++++------- font.lua | 83 ++++++++++++++++++++++++++++++++++++------------ ft.c | 44 ------------------------- helppage.lua | 29 +++++++++-------- inputbox.lua | 8 ++--- reader.lua | 8 ++--- rendertext.lua | 13 ++++---- selectmenu.lua | 27 ++++++++-------- unireader.lua | 6 ++-- 13 files changed, 157 insertions(+), 153 deletions(-) diff --git a/Makefile b/Makefile index 100190348..2eba93c0e 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ FREETYPEDIR=$(MUPDFDIR)/thirdparty/freetype-2.4.8 LFSDIR=luafilesystem # must point to directory with *.ttf fonts for crengine -TTF_FONTS_DIR=/usr/share/fonts/truetype/freefont/ +TTF_FONTS_DIR=$(MUPDFDIR)/fonts # set this to your ARM cross compiler: @@ -119,6 +119,7 @@ lfs.o: $(LFSDIR)/src/lfs.c fetchthirdparty: -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 @@ -128,6 +129,7 @@ fetchthirdparty: cd mupdf/thirdparty/jpeg-*/ && \ patch -N -p0 < ../../../kpvcrlib/jpeg_compress_struct_size.patch &&\ patch -N -p0 < ../../../kpvcrlib/jpeg_decompress_struct_size.patch + 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 @@ -158,7 +160,7 @@ $(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 diff --git a/cre.cpp b/cre.cpp index 27260bd63..319a7af58 100644 --- a/cre.cpp +++ b/cre.cpp @@ -391,12 +391,20 @@ static int drawCurrentPage(lua_State *L) { } } +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} }; @@ -437,23 +445,8 @@ int luaopen_cre(lua_State *L) { luaL_register(L, "cre", cre_func); - /* initialize fonts for CREngine */ - InitFontManager(lString8("./fonts")); - - lString8 fontDir("./fonts"); - LVContainerRef dir = LVOpenDirectory( LocalToUnicode(fontDir).c_str() ); - if ( !dir.isNull() ) - for ( int i=0; iGetObjectCount(); i++ ) { - const LVContainerItemInfo * item = dir->GetObjectInfo(i); - lString16 fileName = item->GetName(); - if ( !item->IsContainer() && fileName.length()>4 && lString16(fileName, fileName.length()-4, 4)==L".ttf" ) { - lString8 fn = UnicodeToLocal(fileName); - printf("loading font: %s\n", fn.c_str()); - if ( !fontMan->RegisterFont(fn) ) { - printf(" failed\n"); - } - } - } + /* initialize font manager for CREngine */ + InitFontManager(lString8()); #ifdef DEBUG_CRENGINE CRLog::setStdoutLogger(); diff --git a/crereader.lua b/crereader.lua index 81e55dc73..4578edc19 100644 --- a/crereader.lua +++ b/crereader.lua @@ -1,3 +1,4 @@ +require "font" require "unireader" require "inputbox" require "selectmenu" @@ -13,6 +14,14 @@ CREReader = UniReader:new{ 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 @@ -182,12 +191,12 @@ function CREReader:_drawReadingInfo() fb.bb:paintRect(0, ypos, G_width, 50, 0) ypos = ypos + 15 - local face, fhash = Font:getFaceAndHash(22) + 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, fhash, + renderUtf8Text(fb.bb, 10, ypos+6, face, "Position: "..load_percent.."%".." "..cur_section, true) ypos = ypos + 15 diff --git a/filechooser.lua b/filechooser.lua index 301cd052b..e96939035 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -122,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) @@ -132,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 @@ -173,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 diff --git a/filesearcher.lua b/filesearcher.lua index 415dac45f..6a9e29831 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -177,13 +177,13 @@ function FileSearcher:addAllCommands() self.commands:add(KEY_F, nil, "F", "font menu", function(self) - 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, G_height) + local re, font = fonts_menu:choose(0, G_height) if re then - Font.cfont = Font.fonts[re] + Font.fontmap["cfont"] = font Font:update() end self.pagedirty = true @@ -227,25 +227,25 @@ function FileSearcher:choose(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 self.pagedirty then self.markerdirty = true fb.bb:paintRect(0, 0, width, height, 0) -- draw menu title - renderUtf8Text(fb.bb, 30, 0 + self.title_H, tface, tfhash, + 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 = self.title_H + self.spacing * 2 - renderUtf8Text(fb.bb, 20, y, cface, cfhash, + 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) self.markerdirty = false else -- found something, draw it @@ -253,7 +253,7 @@ function FileSearcher:choose(keywords) local i = (self.page - 1) * self.perpage + c if i <= self.items then y = self.title_H + (self.spacing * c) - renderUtf8Text(fb.bb, 50, y, cface, cfhash, + renderUtf8Text(fb.bb, 50, y, cface, self.result[i].name, true) end end @@ -263,7 +263,7 @@ function FileSearcher:choose(keywords) y = self.title_H + (self.spacing * self.perpage) + self.foot_H x = (width / 2) - 50 all_page = (math.floor(self.items / self.perpage)+1) - renderUtf8Text(fb.bb, x, y, fface, ffhash, + renderUtf8Text(fb.bb, x, y, fface, "Page "..self.page.." of "..all_page, true) end diff --git a/font.lua b/font.lua index ef31d6277..5c0f3a584 100644 --- a/font.lua +++ b/font.lua @@ -1,23 +1,40 @@ 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", + }, + + 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 +43,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 { 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 diff --git a/ft.c b/ft.c index 151ba4c48..0dc1f496c 100644 --- a/ft.c +++ b/ft.c @@ -56,49 +56,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_find_substitute_font(1, 0, 0, 0, &size); - } else if(!strcmp("sans", fontname)) { - fontdata = pdf_find_substitute_font(0, 0, 0, 0, &size); - } else if(!strcmp("cjk", fontname)) { - fontdata = pdf_find_substitute_cjk_font(0, 0, &size); - } else { - fontdata = pdf_find_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); @@ -192,7 +149,6 @@ static const struct luaL_Reg ft_face_meth[] = { static const struct luaL_Reg ft_func[] = { {"newFace", newFace}, - {"newBuiltinFace", newBuiltinFace}, {NULL, NULL} }; diff --git a/helppage.lua b/helppage.lua index cedb17906..25b9b0a9d 100644 --- a/helppage.lua +++ b/helppage.lua @@ -15,20 +15,23 @@ HelpPage = { -- 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 keys -HelpPage.fsize = 20 -HelpPage.face, HelpPage.fhash = Font:getFaceAndHash(HelpPage.fsize, "mono") --- font for displaying help messages -HelpPage.hfsize = 20 -HelpPage.hface, HelpPage.hfhash = Font:getFaceAndHash(HelpPage.hfsize, "sans") --- font for paging display -HelpPage.ffsize = 15 -HelpPage.fface, HelpPage.ffhash = Font:getFaceAndHash(HelpPage.ffsize, "sans") function HelpPage:show(ypos, height, commands) self.commands = {} @@ -55,17 +58,17 @@ function HelpPage:show(ypos, height, commands) for c = 1, perpage do local i = (self.page - 1) * perpage + c if i <= self.items then - local pen_x = renderUtf8Text(fb.bb, 5, ypos + self.spacing*c, self.face, self.fhash, self.commands[i].shortcut, true) + local pen_x = renderUtf8Text(fb.bb, 5, ypos + self.spacing*c, self.face, self.commands[i].shortcut, true) max_x = math.max(max_x, pen_x) end end for c = 1, perpage do local i = (self.page - 1) * perpage + c if i <= self.items then - renderUtf8Text(fb.bb, max_x + 20, ypos + self.spacing*c, self.hface, self.hfhash, self.commands[i].help, true) + renderUtf8Text(fb.bb, max_x + 20, ypos + self.spacing*c, self.hface, self.commands[i].help, true) end end - renderUtf8Text(fb.bb, 5, height - math.floor(self.ffsize * 0.4), self.fface, self.ffhash, + renderUtf8Text(fb.bb, 5, height - math.floor(self.ffsize * 0.4), self.fface, "Page "..self.page.." of "..math.ceil(self.items / perpage).." - click Back to close this page", true) markerdirty = true end diff --git a/inputbox.lua b/inputbox.lua index fb942f1bf..afa9603b3 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -1,3 +1,4 @@ +require "font" require "rendertext" require "keys" require "graphics" @@ -21,8 +22,7 @@ InputBox = { -- font for displaying input content -- we have to use mono here for better distance controlling - face = freetype.newBuiltinFace("mono", 25), - fhash = "m25", + face = Font:getFace("infont", 25), fheight = 25, fwidth = 15, commands = nil, @@ -34,7 +34,7 @@ function InputBox:refreshText() 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.fhash, + self.face, self.input_string, 0) end @@ -97,7 +97,7 @@ 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 diff --git a/reader.lua b/reader.lua index 934c09f2f..f28596b27 100755 --- a/reader.lua +++ b/reader.lua @@ -117,9 +117,9 @@ 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 @@ -157,7 +157,7 @@ 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 diff --git a/rendertext.lua b/rendertext.lua index 1d95598e3..57f4be75b 100644 --- a/rendertext.lua +++ b/rendertext.lua @@ -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] = { @@ -43,7 +44,7 @@ function clearGlyphCache() glyphcache = {} end -function renderUtf8Text(buffer, x, y, face, facehash, text, kerning) +function renderUtf8Text(buffer, x, y, face, text, kerning) if text == nil then print("# renderUtf8Text called without text"); return @@ -55,9 +56,9 @@ function renderUtf8Text(buffer, x, y, face, facehash, text, kerning) 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 buffer:addblitFrom(glyph.bb, x + pen_x + glyph.l, y - glyph.t, 0, 0, glyph.bb:getWidth(), glyph.bb:getHeight()) else diff --git a/selectmenu.lua b/selectmenu.lua index 9a7b6ec1e..0869644ad 100644 --- a/selectmenu.lua +++ b/selectmenu.lua @@ -12,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, @@ -22,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, @@ -231,9 +230,9 @@ function SelectMenu:choose(ypos, height) 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 self.pagedirty then self.markerdirty = true @@ -243,16 +242,16 @@ function SelectMenu:choose(ypos, height) 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) self.markerdirty = false self:clearCommands() @@ -272,16 +271,16 @@ 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 -- EOF if i <= self.items end -- EOF for @@ -291,7 +290,7 @@ function SelectMenu:choose(ypos, height) 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, + renderUtf8Text(fb.bb, x, y, fface, "Page "..self.page.." of ".. (math.ceil(self.items / self.perpage)), true) end @@ -339,7 +338,7 @@ function SelectMenu:choose(ypos, height) if self.selected_item ~= nil then print("# selected "..self.selected_item) - return self.selected_item + return self.selected_item, self.item_array[self.selected_item] end end -- EOF if end -- EOF while diff --git a/unireader.lua b/unireader.lua index ad14cd5d2..7796509da 100644 --- a/unireader.lua +++ b/unireader.lua @@ -858,11 +858,11 @@ end function UniReader:_drawReadingInfo() local width, height = G_width, G_height local load_percent = (self.pageno / self.doc:getPages()) - local face, fhash = Font:getFaceAndHash(22) + local face = Font:getFace("cfont", 22) -- display memory on top of page fb.bb:paintRect(0, 0, width, 15+6*2, 0) - renderUtf8Text(fb.bb, 10, 15+6, face, fhash, + renderUtf8Text(fb.bb, 10, 15+6, face, "Memory: ".. math.ceil( self.cache_current_memsize / 1024 ).."/"..math.ceil( self.cache_max_memsize / 1024 ).. " "..math.ceil( self.doc:getCacheSize() / 1024 ).."/"..math.ceil( self.cache_document_size / 1024 ).." k", @@ -876,7 +876,7 @@ function UniReader:_drawReadingInfo() if cur_section ~= "" then cur_section = "Section: "..cur_section end - renderUtf8Text(fb.bb, 10, ypos+6, face, fhash, + renderUtf8Text(fb.bb, 10, ypos+6, face, "Page: "..self.pageno.."/"..self.doc:getPages().. " "..cur_section, true) From b32c3bef1ffc9ef07d54c7f8c8bd51c0bfd41662 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 19:13:35 +0200 Subject: [PATCH 125/183] added bind-mounting of host fonts --- Makefile | 1 + launchpad/kpdf.sh | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Makefile b/Makefile index 2eba93c0e..edfc21dc9 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,7 @@ customupdate: all mkdir $(INSTALL_DIR)/data cp -rpL data/*.css $(INSTALL_DIR)/data cp -rp fonts $(INSTALL_DIR) + mkdir -p $(INSTALL_DIR)/fonts/host zip -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" diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index c1f42952e..a2b714b75 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -5,7 +5,12 @@ echo unlock > /proc/keypad echo unlock > /proc/fiveway cd /mnt/us/kindlepdfviewer/ + +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 +grep /mnt/us/kindlepdfviewer/fonts/host /proc/mounts && umount /mnt/us/kindlepdfviewer/fonts/host + killall -cont cvm echo 1 > /proc/eink_fb/update_display From 2f11b761b0d70a78bcbc77e174a11fdbd68ae72d Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 19:32:08 +0200 Subject: [PATCH 126/183] fixed font copying upon customupdate --- Makefile | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index edfc21dc9..eb1acfc28 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,9 @@ 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 +CC:=arm-none-linux-gnueabi-gcc +CXX:=arm-none-linux-gnueabi-g++ +HOST:=arm-none-linux-gnueabi ifdef SBOX_UNAME_MACHINE CC:=gcc CXX:=g++ @@ -26,7 +26,9 @@ endif HOSTCC:=gcc HOSTCXX:=g++ -CFLAGS:=-O3 +CFLAGS:=-O3 --sysroot=/home/hw/x-tools/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/sys-root +CXXFLAGS:=-O3 --sysroot=/home/hw/x-tools/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/sys-root +LDFLAGS:=--sysroot=/home/hw/x-tools/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/sys-root ARM_CFLAGS:=-march=armv6 # use this for debugging: #CFLAGS:=-O0 -g @@ -79,7 +81,7 @@ LUALIB := $(LUADIR)/src/liblua.a all:kpdfview kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) - $(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) -lstdc++ \ + $(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) \ kpdfview.o \ einkfb.o \ pdf.o \ @@ -96,6 +98,7 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft $(DJVULIBS) \ cre.o \ $(CRENGINELIBS) \ + libstdc++.a \ -o kpdfview slider_watcher: slider_watcher.c @@ -197,8 +200,8 @@ customupdate: all cp -p README.TXT COPYING kpdfview *.lua $(INSTALL_DIR) mkdir $(INSTALL_DIR)/data cp -rpL data/*.css $(INSTALL_DIR)/data - cp -rp fonts $(INSTALL_DIR) - mkdir -p $(INSTALL_DIR)/fonts/host + cp -rpL fonts $(INSTALL_DIR) + mkdir $(INSTALL_DIR)/fonts/host zip -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" From 538940baf78527137a1151f1408140e3d1b53932 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 19:37:47 +0200 Subject: [PATCH 127/183] reworked toolchain management a bit you can now set the toolchain by using e.g. make HOST=arm-unknown-linux-gnueabi or make HOST=arm-linux-gnueabi also, a SYSROOT flag is introduced. I use this to point the compiler to a certain sysroot (which contains headers and might contain libraries to link against). I use this to use a current and modern compiler against the older software versions on the Kindle (especially glibc). I use it like this: make HOST=arm-none-linux-gnueabi SYSROOT="--sysroot=/home/hw/my-kindle-sysroot" --- Makefile | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index eb1acfc28..25fc6e88e 100644 --- a/Makefile +++ b/Makefile @@ -16,9 +16,10 @@ TTF_FONTS_DIR=$(MUPDFDIR)/fonts # set this to your ARM cross compiler: -CC:=arm-none-linux-gnueabi-gcc -CXX:=arm-none-linux-gnueabi-g++ HOST:=arm-none-linux-gnueabi +CC:=$(HOST)-gcc +CXX:=$(HOST)-g++ +STRIP:=$(HOST)-strip ifdef SBOX_UNAME_MACHINE CC:=gcc CXX:=g++ @@ -26,9 +27,9 @@ endif HOSTCC:=gcc HOSTCXX:=g++ -CFLAGS:=-O3 --sysroot=/home/hw/x-tools/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/sys-root -CXXFLAGS:=-O3 --sysroot=/home/hw/x-tools/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/sys-root -LDFLAGS:=--sysroot=/home/hw/x-tools/arm-unknown-linux-gnueabi/arm-unknown-linux-gnueabi/sys-root +CFLAGS:=-O3 $(SYSROOT) +CXXFLAGS:=-O3 $(SYSROOT) +LDFLAGS:= $(SYSROOT) ARM_CFLAGS:=-march=armv6 # use this for debugging: #CFLAGS:=-O0 -g @@ -195,6 +196,8 @@ VERSION?=$(shell git rev-parse --short HEAD) 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) @@ -202,6 +205,6 @@ customupdate: all cp -rpL data/*.css $(INSTALL_DIR)/data cp -rpL fonts $(INSTALL_DIR) mkdir $(INSTALL_DIR)/fonts/host - zip -r kindlepdfviewer-$(VERSION).zip $(INSTALL_DIR) launchpad/ + 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" From a0c0c980936b37e17eba5f16b9f9513ce93e53ec Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 19:46:10 +0200 Subject: [PATCH 128/183] added configurable static libstdc++ linking you can now point to a static libstdc++: make STATICLIBSTDCPP=/home/hw/kindle-sysroot/usr/lib/libstdc++.a --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 25fc6e88e..c276a1a2c 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,11 @@ 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. @@ -82,7 +87,7 @@ LUALIB := $(LUADIR)/src/liblua.a all:kpdfview kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) - $(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) \ + $(CC) -lm -ldl -lpthread $(EMU_LDFLAGS) $(DYNAMICLIBSTDCPP) \ kpdfview.o \ einkfb.o \ pdf.o \ @@ -99,7 +104,7 @@ kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft $(DJVULIBS) \ cre.o \ $(CRENGINELIBS) \ - libstdc++.a \ + $(STATICLIBSTDCPP) \ -o kpdfview slider_watcher: slider_watcher.c From 0b0dfa24ed104226c224293d0e371605b0b51e46 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 19:52:58 +0200 Subject: [PATCH 129/183] removed obsolete example --- rendertext_example.lua | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100755 rendertext_example.lua diff --git a/rendertext_example.lua b/rendertext_example.lua deleted file mode 100755 index 3c208b8f5..000000000 --- a/rendertext_example.lua +++ /dev/null @@ -1,27 +0,0 @@ -#!./kpdfview -require "rendertext" -require "graphics" - -fb = einkfb.open("/dev/fb0") -G_width, G_height = fb:getSize() - -print("open") - -face = freetype.newBuiltinFace("sans", 64) ---face = freetype.newFace("test.ttf", 64) -print("got face") - -if face:hasKerning() then - print("has kerning") -end - -fb.bb:paintRect(1,1,599,300,7); - -renderUtf8Text(fb.bb, 100, 100, face, "h", "AV T.T: gxyt!", true) -renderUtf8Text(fb.bb, 100, 200, face, "h", "AV T.T: gxyt!", false) - -fb:refresh() - -while true do - local ev = input.waitForEvent() -end From 1c50a5676a5ed3fdcb5d33d760cbadf6ca3e9084 Mon Sep 17 00:00:00 2001 From: HW Date: Mon, 9 Apr 2012 19:58:34 +0200 Subject: [PATCH 130/183] free blitbuffers when cleaning complete cache --- unireader.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/unireader.lua b/unireader.lua index 7796509da..cf3868a41 100644 --- a/unireader.lua +++ b/unireader.lua @@ -355,6 +355,9 @@ end -- blank the cache function UniReader:clearCache() + for k, _ in pairs(self.cache) do + self.cache[k].bb:free() + end self.cache = {} self.cache_current_memsize = 0 end From 75bed710f74965d006dc67b60949312cb4ffb399 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Mon, 9 Apr 2012 20:25:40 +0200 Subject: [PATCH 131/183] fix shift direction and cleanup comments --- blitbuffer.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/blitbuffer.c b/blitbuffer.c index 5d0a7b8b3..123b9cb55 100644 --- a/blitbuffer.c +++ b/blitbuffer.c @@ -443,7 +443,7 @@ static int dimRect(lua_State *L) { } if(x & 1) { - /* This will invert the leftmost column + /* This will dimm the leftmost column * in the case when x is odd. After this, * x will become even. */ dstptr = (uint8_t*)(dst->data + @@ -451,7 +451,7 @@ static int dimRect(lua_State *L) { x / 2); for(cy = 0; cy < h; cy++) { int px = *dstptr & 0x0F; - *dstptr &= 0xF0 | px << 1; + *dstptr &= 0xF0 | px >> 1; dstptr += dst->pitch; } x++; @@ -469,7 +469,7 @@ static int dimRect(lua_State *L) { dstptr += dst->pitch; } if(w & 1) { - /* This will invert the rightmost column + /* This will dimm the rightmost column * in the case when (w & 1) && !(x & 1) or * !(w & 1) && (x & 1). */ dstptr = (uint8_t*)(dst->data + From 95998a7397767c53cdbf3924ebb1d587952df37b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 10 Apr 2012 15:52:51 +0800 Subject: [PATCH 132/183] add: NumInputBox class for goto page feature --- inputbox.lua | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ unireader.lua | 2 +- 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/inputbox.lua b/inputbox.lua index afa9603b3..a38950022 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -3,6 +3,11 @@ require "rendertext" require "keys" require "graphics" + +---------------------------------------------------- +-- General inputbox +---------------------------------------------------- + InputBox = { -- Class vars: h = 100, @@ -28,6 +33,13 @@ InputBox = { commands = nil, } +function InputBox:new(o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + function InputBox:refreshText() -- clear previous painted text fb.bb:paintRect(140, self.input_start_y-19, @@ -91,6 +103,10 @@ function InputBox:clearText() self.input_slot_w, self.h-25) end +function InputBox:drawHelpMsg(ypos, w, h) + return +end + function InputBox:drawBox(ypos, w, h, title) -- draw input border fb.bb:paintRect(20, ypos, w, h, 5) @@ -127,6 +143,7 @@ function InputBox:input(ypos, height, title, d_text) -- 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 @@ -241,3 +258,92 @@ function InputBox:addAllCommands() end ) end + + +---------------------------------------------------- +-- Inputbox for numbers only +-- Designed by eLiNK +---------------------------------------------------- + +NumInputBox = InputBox:new{} + +function NumInputBox:addAllCommands() + if self.commands then + -- we only initialize once + return + end + 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_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 InputBox: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 diff --git a/unireader.lua b/unireader.lua index b856f606d..d84d697e1 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1081,7 +1081,7 @@ function UniReader:addAllCommands() self.commands:add(KEY_G,nil,"G", "goto page", function(unireader) - local page = InputBox:input(G_height-100, 100, "Page:") + local page = NumInputBox:input(G_height-100, 100, "Page:") -- convert string to number if not pcall(function () page = page + 0 end) then page = unireader.pageno From 485dda2a0f039ac10ebd45244e2f074aeef81cbf Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 10 Apr 2012 16:35:46 +0800 Subject: [PATCH 133/183] fix: bugs in inputbox.lua * one typo * clear ret_code on exit * add init() method --- inputbox.lua | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/inputbox.lua b/inputbox.lua index a38950022..c26b9e20f 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -31,6 +31,7 @@ InputBox = { fheight = 25, fwidth = 15, commands = nil, + initialized = false, } function InputBox:new(o) @@ -40,6 +41,13 @@ function InputBox:new(o) return o end +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, @@ -125,8 +133,8 @@ end -- @d_text: default to nil (used to set default text in input slot) ---------------------------------------------------------------------- function InputBox:input(ypos, height, title, d_text) + self:init() -- do some initilization - self:addAllCommands() self.ypos = ypos self.h = height self.input_start_y = ypos + 35 @@ -170,6 +178,7 @@ function InputBox:input(ypos, height, title, d_text) end if ret_code == "break" then + ret_code = nil break end end -- if @@ -265,13 +274,12 @@ end -- Designed by eLiNK ---------------------------------------------------- -NumInputBox = InputBox:new{} +NumInputBox = InputBox:new{ + initialized = false, + commands = Commands:new{}, +} function NumInputBox:addAllCommands() - if self.commands then - -- we only initialize once - return - end self.commands = Commands:new{} INPUT_NUM_KEYS = { @@ -320,7 +328,7 @@ function NumInputBox:addAllCommands() ) end -function InputBox:drawHelpMsg(ypos, w, h) +function NumInputBox:drawHelpMsg(ypos, w, h) local w = 415 local y = ypos - 60 local x = (G_width - w) / 2 From c85b768c0ecc43c000e027aa48896c49b510d892 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 10 Apr 2012 20:45:22 +0800 Subject: [PATCH 134/183] switched shortcut between 10 degree and 90 degree rotation * also deleted 10 degree rotation shortcut in djvureader, becuase it is not supported by the library. --- commands.lua | 4 ++-- djvureader.lua | 3 +++ unireader.lua | 10 +++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/commands.lua b/commands.lua index abdb19cfe..b818f258b 100644 --- a/commands.lua +++ b/commands.lua @@ -162,7 +162,7 @@ function Commands:new(obj) end setmetatable(obj.map, mt) - obj:add(KEY_INTO_SCREEN_SAVER, nil, "slider", + obj:add(KEY_INTO_SCREEN_SAVER, nil, "Slider", "toggle screen saver", function() Screen:saveCurrentBB() @@ -171,7 +171,7 @@ function Commands:new(obj) --os.execute("killall -cont cvm") end ) - obj:add(KEY_OUTOF_SCREEN_SAVER, nil, "slider", + obj:add(KEY_OUTOF_SCREEN_SAVER, nil, "Slider", "toggle screen saver", function() os.execute("sleep 3") diff --git a/djvureader.lua b/djvureader.lua index 460b2167d..9d92229c6 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -19,6 +19,9 @@ function DJVUReader:init() end function DJVUReader:adjustDjvuReaderCommand() + self.commands:del(KEY_J, MOD_SHIFT, "J") + self.commands:del(KEY_K, MOD_SHIFT, "K") + self.commands:add(KEY_N, nil, "N", "start highlight mode", function(unireader) diff --git a/unireader.lua b/unireader.lua index d84d697e1..551638a11 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1006,7 +1006,7 @@ function UniReader:addAllCommands() function(unireader) unireader:setGlobalZoom(unireader.globalzoom-unireader.globalzoom_orig*0.2) end) - self.commands:add(KEY_BACK,nil,"back", + self.commands:add(KEY_BACK,nil,"Back", "back to last jump", function(unireader) if #unireader.jump_stack ~= 0 then @@ -1113,22 +1113,22 @@ function UniReader:addAllCommands() function(unireader) unireader:addJump(unireader.pageno) end) - self.commands:add(KEY_J,nil,"J", + self.commands:add(KEY_J,MOD_SHIFT,"J", "rotate 10° clockwise", function(unireader) unireader:setRotate( unireader.globalrotate + 10 ) end) - self.commands:add(KEY_J,MOD_SHIFT,"J", + self.commands:add(KEY_J,nil,"J", "rotate screen 90° clockwise", function(unireader) unireader:screenRotate("clockwise") end) - self.commands:add(KEY_K,nil,"K", + self.commands:add(KEY_K,MOD_SHIFT,"K", "rotate 10° counterclockwise", function(unireader) unireader:setRotate( unireader.globalrotate - 10 ) end) - self.commands:add(KEY_K,MOD_SHIFT,"K", + self.commands:add(KEY_K,nil,"K", "rotate screen 90° counterclockwise", function(unireader) unireader:screenRotate("anticlockwise") From 1aba2d2695f9103454b05fb1c56775d55e36f24c Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Tue, 10 Apr 2012 15:05:13 +0200 Subject: [PATCH 135/183] fix operator precedence #95 This explains why our dimmed areas had vertical stripes and improves quality --- blitbuffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blitbuffer.c b/blitbuffer.c index 123b9cb55..13eb14a9e 100644 --- a/blitbuffer.c +++ b/blitbuffer.c @@ -463,8 +463,8 @@ static int dimRect(lua_State *L) { for(cy = 0; cy < h; cy++) { for(cx = 0; cx < w/2; cx++) { *(dstptr+cx) = - *(dstptr+cx) >> 1 & 0xF0 | - *(dstptr+cx) & 0x0F >> 1; + ( *(dstptr+cx) >> 1 ) & 0xF0 | + ( *(dstptr+cx) & 0x0F ) >> 1; } dstptr += dst->pitch; } From 7093e7377e1f98451683c063d0797641baea1092 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 10 Apr 2012 21:34:38 +0800 Subject: [PATCH 136/183] fix: reset dc after screen rotate handle ZOOM_FIT_TO_CONTENT_WIDTH_PAN mode seperately. --- crereader.lua | 5 +++++ unireader.lua | 13 +++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crereader.lua b/crereader.lua index a1f77deec..725883872 100644 --- a/crereader.lua +++ b/crereader.lua @@ -85,6 +85,11 @@ function CREReader:redrawCurrentPage() self:goto(self.pos) end +-- there is no zoom mode in CREReader +function CREReader:setGlobalZoomMode() + return +end + ---------------------------------------------------- -- goto related methods ---------------------------------------------------- diff --git a/unireader.lua b/unireader.lua index 551638a11..1008989fe 100644 --- a/unireader.lua +++ b/unireader.lua @@ -462,7 +462,7 @@ function UniReader:setzoom(page, preCache) if self.content_top == -2012 then -- We must handle previous page turn as a special cases, -- because we want to arrive at the bottom of previous page. - -- Since this a real page turn, we need to recalcunate stuff. + -- Since this a real page turn, we need to recalculate stuff. if (x1 - x0) < pwidth then self.globalzoom = width / (x1 - x0) end @@ -758,7 +758,6 @@ function UniReader:screenRotate(orien) -- update global width and height variable G_width, G_height = fb:getSize() self:clearCache() - self:redrawCurrentPage() end function UniReader:cleanUpTocTitle(title) @@ -1122,6 +1121,11 @@ function UniReader:addAllCommands() "rotate screen 90° clockwise", function(unireader) unireader:screenRotate("clockwise") + if self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_WIDTH_PAN then + self:setGlobalZoomMode(self.ZOOM_FIT_TO_CONTENT_WIDTH) + else + self:redrawCurrentPage() + end end) self.commands:add(KEY_K,MOD_SHIFT,"K", "rotate 10° counterclockwise", @@ -1132,6 +1136,11 @@ function UniReader:addAllCommands() "rotate screen 90° counterclockwise", function(unireader) unireader:screenRotate("anticlockwise") + if self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_WIDTH_PAN then + self:setGlobalZoomMode(self.ZOOM_FIT_TO_CONTENT_WIDTH) + else + self:redrawCurrentPage() + end end) self.commands:add(KEY_R, MOD_SHIFT, "R", "manual full screen refresh", From 6041603468a6cf24f0b3edbd56bd22277dabc722 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Tue, 10 Apr 2012 21:40:29 +0800 Subject: [PATCH 137/183] fix: use new width and height drawing after screen rotate --- filesearcher.lua | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/filesearcher.lua b/filesearcher.lua index 9988378cf..efc6e2650 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -214,8 +214,7 @@ function FileSearcher:addAllCommands() end function FileSearcher:choose(keywords) - local width, height = G_width, G_height - self.perpage = math.floor(height / self.spacing) - 2 + self.perpage = math.floor(G_height / self.spacing) - 2 self.pagedirty = true self.markerdirty = false @@ -233,7 +232,7 @@ function FileSearcher:choose(keywords) if self.pagedirty then self.markerdirty = true - fb.bb:paintRect(0, 0, width, height, 0) + fb.bb:paintRect(0, 0, G_width, G_height, 0) -- draw menu title renderUtf8Text(fb.bb, 30, 0 + self.title_H, tface, @@ -261,7 +260,7 @@ function FileSearcher:choose(keywords) -- draw footer y = self.title_H + (self.spacing * self.perpage) + self.foot_H - x = (width / 2) - 50 + 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) @@ -271,22 +270,22 @@ function FileSearcher:choose(keywords) if not self.pagedirty then if self.oldcurrent > 0 then y = self.title_H + (self.spacing * self.oldcurrent) + 10 - fb.bb:paintRect(30, y, width - 60, 3, 0) - fb:refresh(1, 30, y, width - 60, 3) + 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 = self.title_H + (self.spacing * self.current) + 10 - fb.bb:paintRect(30, y, width - 60, 3, 15) + fb.bb:paintRect(30, y, G_width - 60, 3, 15) if not self.pagedirty then - fb:refresh(1, 30, y, width - 60, 3) + fb:refresh(1, 30, y, G_width - 60, 3) end self.oldcurrent = self.current self.markerdirty = false end if self.pagedirty then - fb:refresh(0, 0, 0, width, height) + fb:refresh(0) self.pagedirty = false end From b555596f426309e46de28fa4b567030630d0c36a Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Tue, 10 Apr 2012 23:15:09 +0200 Subject: [PATCH 138/183] correctly dim part of page when hitting margins --- unireader.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/unireader.lua b/unireader.lua index 1008989fe..8b9e0887f 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1248,6 +1248,9 @@ function UniReader:addAllCommands() elseif keydef.keycode == KEY_FW_UP then unireader.offset_y = unireader.offset_y + y if unireader.offset_y > 0 then + if unireader.pan_by_page then + unireader.show_overlap = unireader.offset_y + unireader.pan_overlap_vertical + end unireader.offset_y = 0 elseif unireader.pan_by_page then unireader.show_overlap = unireader.pan_overlap_vertical -- bottom @@ -1255,6 +1258,9 @@ function UniReader:addAllCommands() elseif keydef.keycode == KEY_FW_DOWN then unireader.offset_y = unireader.offset_y - y if unireader.offset_y < unireader.min_offset_y then + if unireader.pan_by_page then + unireader.show_overlap = unireader.offset_y + y - unireader.min_offset_y - G_height + end unireader.offset_y = unireader.min_offset_y elseif unireader.pan_by_page then unireader.show_overlap = -unireader.pan_overlap_vertical -- top From e42530bbc0ed97064b84e3d2259adb74cd732b1b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 11 Apr 2012 17:06:37 +0800 Subject: [PATCH 139/183] mod: set DrawBuf to 4bpp mode in cre.cpp --- cre.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cre.cpp b/cre.cpp index 319a7af58..0f6ca672a 100644 --- a/cre.cpp +++ b/cre.cpp @@ -26,7 +26,6 @@ extern "C" { #include "crengine.h" -//using namespace std; typedef struct CreDocument { LVDocView *text_view; @@ -47,7 +46,8 @@ static int openDocument(lua_State *L) { lua_setmetatable(L, -2); doc->text_view = new LVDocView(); - doc->text_view->setBackgroundColor(0x000000); + //doc->text_view->setBackgroundColor(0xFFFFFF); + //doc->text_view->setTextColor(0x000000); if (LVLoadStylesheetFile(lString16(style_sheet), css)){ if (!css.empty()){ doc->text_view->setStyleSheet(css); @@ -59,7 +59,6 @@ static int openDocument(lua_State *L) { doc->dom_doc = doc->text_view->getDocument(); doc->text_view->Render(); - return 1; } @@ -308,7 +307,7 @@ static int gotoXPointer(lua_State *L) { 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 correctionColor + * 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); @@ -367,21 +366,22 @@ static int drawCurrentPage(lua_State *L) { int w = bb->w, h = bb->h; - LVGrayDrawBuf drawBuf(w, h, 8); + /* 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++) { - bbptr[x] = 255 - (((pmptr[x*2 + 1] & 0xF0) >> 4) | - (pmptr[x*2] & 0xF0)); + /* 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); @@ -389,6 +389,8 @@ static int drawCurrentPage(lua_State *L) { bbptr += bb->pitch; pmptr += w; } + + return 0; } static int registerFont(lua_State *L) { From 3bf6860767c35f1f4f010b39817ab35028408b69 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 11 Apr 2012 17:07:31 +0800 Subject: [PATCH 140/183] fix: a terrible mistake in kpvcrlib's CMakeLists.txt add LINUX & _LINUX macro definition. also stop building antiword. --- kpvcrlib/CMakeLists.txt | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/kpvcrlib/CMakeLists.txt b/kpvcrlib/CMakeLists.txt index 19bafa56b..f148b2c1c 100644 --- a/kpvcrlib/CMakeLists.txt +++ b/kpvcrlib/CMakeLists.txt @@ -1,3 +1,4 @@ + PROJECT(kpvcrlib) cmake_minimum_required(VERSION 2.6) @@ -8,7 +9,8 @@ SET(CR_3RDPARTY_DIR crengine/thirdparty) SET(CR3_PNG 1) #SET(CR3_JPEG 1) -SET(FREETYPE_INCLUDE_DIRS ${CR_3RDPARTY_DIR}/freetype/include) +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) @@ -28,7 +30,8 @@ INCLUDE_DIRECTORIES( ${JCONFIG_INCLUDE_DIR} ) -ADD_DEFINITIONS(-DUSE_FONTCONFIG=0 -DUSE_FREETYPE=1 -DCR3_PATCH=1 -DNDEBUG=1) +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) @@ -40,12 +43,15 @@ ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/libpng) #message("Will build patched JPEGLIB library") #ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/libjpeg) -message("Will build patched ANTIWORD library") -ADD_DEFINITIONS(-DENABLE_ANTIWORD=1) -ADD_DEFINITIONS(-DCR3_ANTIWORD_PATCH=1) -ADD_SUBDIRECTORY(${CR_3RDPARTY_DIR}/antiword) +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) + From e3f6bab9f16824529719e37fb7aa523a8040525b Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 11 Apr 2012 17:13:37 +0800 Subject: [PATCH 141/183] mod: adapt Makefile to new kpvcrlib's CMake environment --- Makefile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index c276a1a2c..6fba6d2c5 100644 --- a/Makefile +++ b/Makefile @@ -71,7 +71,8 @@ DJVULIBS := $(DJVUDIR)/build/libdjvu/.libs/libdjvulibre.a CRENGINELIBS := $(CRENGINEDIR)/crengine/libcrengine.a \ $(CRENGINEDIR)/thirdparty/chmlib/libchmlib.a \ $(CRENGINEDIR)/thirdparty/libpng/libpng.a \ - $(CRENGINEDIR)/thirdparty/antiword/libantiword.a +# we don't support dictionary lookup corrently + #$(CRENGINEDIR)/thirdparty/antiword/libantiword.a THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \ $(MUPDFLIBDIR)/libopenjpeg.a \ $(MUPDFLIBDIR)/libjbig2dec.a \ @@ -79,8 +80,8 @@ THIRDPARTYLIBS := $(MUPDFLIBDIR)/libfreetype.a \ $(MUPDFLIBDIR)/libz.a #@TODO patch crengine to use the latest libjpeg 04.04 2012 (houqp) - #$(MUPDFLIBDIR)/libjpeg.a - #$(CRENGINEDIR)/thirdparty/libjpeg/libjpeg.a + #$(MUPDFLIBDIR)/libjpeg.a \ + #$(CRENGINEDIR)/thirdparty/libjpeg/libjpeg.a \ LUALIB := $(LUADIR)/src/liblua.a @@ -133,11 +134,15 @@ fetchthirdparty: 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 From 0007a8677f98dcd014f6811727bed68c1552f7f9 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Wed, 11 Apr 2012 17:32:36 +0800 Subject: [PATCH 142/183] fix: rm antiword in make clean --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6fba6d2c5..7ae242008 100644 --- a/Makefile +++ b/Makefile @@ -153,7 +153,7 @@ clean: cleanthirdparty: make -C $(LUADIR) clean make -C $(MUPDFDIR) clean - make -C $(CRENGINEDIR)/thirdparty/antiword clean + #make -C $(CRENGINEDIR)/thirdparty/antiword clean make -C $(CRENGINEDIR)/thirdparty/chmlib clean make -C $(CRENGINEDIR)/thirdparty/libpng clean make -C $(CRENGINEDIR)/crengine clean From 80a6e0210be92a5d9c0ba5299e876463e7e718de Mon Sep 17 00:00:00 2001 From: HW Date: Wed, 11 Apr 2012 21:12:59 +0200 Subject: [PATCH 143/183] add PDF text extraction (for pages) this will return data in the way that djvu.c does already. hopefully, this will permit us to re-use the highlighting code (and factor it out into unireader.lua) --- pdf.c | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/pdf.c b/pdf.c index 87190f14d..7b0a811bf 100644 --- a/pdf.c +++ b/pdf.c @@ -311,6 +311,123 @@ 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; + fz_matrix ctm; + 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"); + + /* returned coordinates are in centi-point (n * 0.01 pt) */ + ctm = fz_scale(100, 100); + + 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, ctm, NULL); + fz_free_device(tdev); + + /* table that contains all the lines */ + lua_newtable(L); + ptr = page_text; + line = 1; + while(ptr) { + /* 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' ) { + // 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++); + + ptr = ptr->next; + } + + 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 +440,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; @@ -456,6 +573,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}, From e772d9f0d7ea73b17d640d3270130e1e6b4f62eb Mon Sep 17 00:00:00 2001 From: HW Date: Wed, 11 Apr 2012 22:47:05 +0200 Subject: [PATCH 144/183] turn back coordinates to be just in pt, not 0,01pt --- pdf.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pdf.c b/pdf.c index 7b0a811bf..96363b42e 100644 --- a/pdf.c +++ b/pdf.c @@ -331,7 +331,6 @@ static int getPageText(lua_State *L) { fz_text_span *ptr; fz_device *tdev; fz_bbox bbox, linebbox; - fz_matrix ctm; int i; int word, line; int len, c; @@ -341,12 +340,9 @@ static int getPageText(lua_State *L) { PdfPage *page = (PdfPage*) luaL_checkudata(L, 1, "pdfpage"); - /* returned coordinates are in centi-point (n * 0.01 pt) */ - ctm = fz_scale(100, 100); - 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, ctm, NULL); + fz_run_page(page->doc->xref, page->page, tdev, fz_identity, NULL); fz_free_device(tdev); /* table that contains all the lines */ From 46d197954b56fc65a8b0e9c7a63e2a6ebe3c2480 Mon Sep 17 00:00:00 2001 From: HW Date: Wed, 11 Apr 2012 22:52:48 +0200 Subject: [PATCH 145/183] put highlight implementation into unireader --- crereader.lua | 2 + djvureader.lua | 602 +---------------------------------------------- pdfreader.lua | 22 ++ unireader.lua | 626 ++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 646 insertions(+), 606 deletions(-) diff --git a/crereader.lua b/crereader.lua index 725883872..a2f67b0e7 100644 --- a/crereader.lua +++ b/crereader.lua @@ -241,6 +241,8 @@ function CREReader:adjustCreReaderCommands() 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_OR_ALT, ">", diff --git a/djvureader.lua b/djvureader.lua index 9d92229c6..38efbedd8 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -21,21 +21,6 @@ end function DJVUReader:adjustDjvuReaderCommand() self.commands:del(KEY_J, MOD_SHIFT, "J") self.commands:del(KEY_K, MOD_SHIFT, "K") - - self.commands:add(KEY_N, nil, "N", - "start highlight mode", - function(unireader) - unireader:startHighLightMode() - unireader:goto(unireader.pageno) - end - ) - self.commands:add(KEY_N, MOD_SHIFT, "N", - "display all highlights", - function(unireader) - unireader:showHighLight() - unireader:goto(unireader.pageno) - end - ) end @@ -49,7 +34,7 @@ end -- down left conner, i.e. y is upside down. This method -- only transform these coordinates. ---------------------------------------------------- -function DJVUReader:_rectCoordTransform(x0, y0, x1, y1) +function DJVUReader:rectCoordTransform(x0, y0, x1, y1) return self.offset_x + x0 * self.globalzoom, self.offset_y + self.cur_full_height - (y1 * self.globalzoom), @@ -57,587 +42,6 @@ function DJVUReader:_rectCoordTransform(x0, y0, x1, y1) (y1 - y0) * self.globalzoom end --- make sure the whole word can be seen in screen -function DJVUReader:_isEntireWordInScreenRange(w) - return self:_isEntireWordInScreenHeightRange(w) and - self:_isEntireWordInScreenWidthRange(w) -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 - -function DJVUReader:_isEntireWordInScreenWidthRange(w) - return (w ~= nil) and - (w.x0 * self.globalzoom >= -self.offset_x) and - (w.x1 * self.globalzoom <= -self.offset_x + G_width) -end - --- make sure at least part of the word can be seen in screen -function DJVUReader:_isWordInScreenRange(w) - return (w ~= nil) and - (self.cur_full_height - (w.y0 * self.globalzoom) >= - -self.offset_y) and - (self.cur_full_height - (w.y1 * self.globalzoom) <= - -self.offset_y + G_height) and - (w.x1 * self.globalzoom >= -self.offset_x) and - (w.x0 * self.globalzoom <= -self.offset_x + G_width) -end - -function DJVUReader:toggleTextHighLight(word_list) - for _,text_item in ipairs(word_list) do - for _,line_item in ipairs(text_item) do - -- make sure that line is in screen range - if self:_isEntireWordInScreenHeightRange(line_item) then - local x, y, w, h = self:_rectCoordTransform( - line_item.x0, line_item.y0, - line_item.x1, line_item.y1) - -- slightly enlarge the highlight height - -- for better viewing experience - x = x - y = y - h * 0.1 - w = w - h = h * 1.2 - - self.highlight.drawer = self.highlight.drawer or "underscore" - if self.highlight.drawer == "underscore" then - self.highlight.line_width = self.highlight.line_width or 2 - self.highlight.line_color = self.highlight.line_color or 5 - fb.bb:paintRect(x, y+h-1, w, - self.highlight.line_width, - self.highlight.line_color) - elseif self.highlight.drawer == "marker" then - fb.bb:invertRect(x, y, w, h) - end - end -- EOF if isEntireWordInScreenHeightRange - end -- EOF for line_item - end -- EOF for text_item -end - -function DJVUReader:_wordIterFromRange(t, l0, w0, l1, w1) - local i = l0 - local j = w0 - 1 - return function() - if i <= l1 then - -- if in line range, loop through lines - if i == l1 then - -- in last line - if j < w1 then - j = j + 1 - else - -- out of range return nil - return nil, nil - end - else - if j < #t[i] then - j = j + 1 - else - -- goto next line - i = i + 1 - j = 1 - end - end - return i, j - end - end -- EOF closure -end - -function DJVUReader:_toggleWordHighLight(t, l, w) - x, y, w, h = self:_rectCoordTransform(t[l][w].x0, t[l].y0, - t[l][w].x1, t[l].y1) - -- slightly enlarge the highlight range for better viewing experience - x = x - w * 0.05 - y = y - h * 0.05 - w = w * 1.1 - h = h * 1.1 - - fb.bb:invertRect(x, y, w, h) -end - -function DJVUReader:_toggleTextHighLight(t, l0, w0, l1, w1) - --print("# toggle range", l0, w0, l1, w1) - -- make sure (l0, w0) is smaller than (l1, w1) - if l0 > l1 then - l0, l1 = l1, l0 - w0, w1 = w1, w0 - elseif l0 == l1 and w0 > w1 then - w0, w1 = w1, w0 - end - - for _l, _w in self:_wordIterFromRange(t, l0, w0, l1, w1) do - if self:_isWordInScreenRange(t[_l][_w]) then - -- blitbuffer module will take care of the out of screen range part. - self:_toggleWordHighLight(t, _l, _w) - end - end -end - --- remember to clear cursor before calling this -function DJVUReader:drawCursorAfterWord(t, l, w) - self.cursor:setHeight((t[l].y1 - t[l].y0) * self.globalzoom) - self.cursor:moveTo( - self.offset_x + t[l][w].x1 * self.globalzoom, - self.offset_y + self.cur_full_height - (t[l].y1 * self.globalzoom)) - self.cursor:draw() -end - -function DJVUReader:drawCursorBeforeWord(t, l, w) - self.cursor:setHeight((t[l].y1 - t[l].y0) - * self.globalzoom) - self.cursor:moveTo( - self.offset_x + t[l][w].x0 * self.globalzoom - self.cursor.w, - self.offset_y + self.cur_full_height - t[l].y1 * self.globalzoom) - self.cursor:draw() -end - -function DJVUReader:startHighLightMode() - local t = self.doc:getPageText(self.pageno) - - local function _findFirstWordInView(t) - for i=1, #t, 1 do - if self:_isEntireWordInScreenRange(t[i][1]) then - return i, 1 - end - end - - return nil - end - - local function _prevWord(t, cur_l, cur_w) - if cur_l == 1 then - if cur_w == 1 then - -- already the first word - return 1, 1 - else - -- in first line, but not first word - return cur_l, cur_w -1 - end - end - - if cur_w <= 1 then - -- first word in current line, goto previous line - return cur_l - 1, #t[cur_l-1] - else - return cur_l, cur_w - 1 - end - end - - local function _nextWord(t, cur_l, cur_w) - if cur_l == #t then - if cur_w == #(t[cur_l]) then - -- already the last word - return cur_l, cur_w - else - -- in last line, but not last word - return cur_l, cur_w + 1 - end - end - - if cur_w < #t[cur_l] then - return cur_l, cur_w + 1 - else - -- last word in current line, move to next line - return cur_l + 1, 1 - end - end - - local function _wordInNextLine(t, cur_l, cur_w) - if cur_l == #t then - -- already in last line, return the last word - return cur_l, #(t[cur_l]) - else - return cur_l + 1, math.min(cur_w, #t[cur_l+1]) - end - end - - local function _wordInPrevLine(t, cur_l, cur_w) - if cur_l == 1 then - -- already in first line, return the first word - return 1, 1 - else - return cur_l - 1, math.min(cur_w, #t[cur_l-1]) - end - end - - local function _isMovingForward(l, w) - return l.cur > l.start or (l.cur == l.start and w.cur > w.start) - end - - local l = {} - local w = {} - - l.start, w.start = _findFirstWordInView(t) - if not l.start then - print("# no text in current view!") - return - end - - l.cur, w.cur = l.start, w.start - l.new, w.new = l.cur, w.cur - local is_meet_start = false - local is_meet_end = false - local running = true - - self.cursor = Cursor:new { - x_pos = t[l.cur][w.cur].x1*self.globalzoom, - y_pos = self.offset_y + (self.cur_full_height - - (t[l.cur][w.cur].y1 * self.globalzoom)), - h = (t[l.cur][w.cur].y1 - t[l.cur][w.cur].y0) * self.globalzoom, - line_width_factor = 4, - } - self.cursor:draw() - fb:refresh(1) - - -- first use cursor to place start pos for highlight - while running do - local ev = input.waitForEvent() - ev.code = adjustKeyEvents(ev) - if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - if ev.code == KEY_FW_LEFT then - if w.cur == 1 then - w.cur = 0 - w.new = 0 - else - if w.cur == 0 then - -- already at the left end of current line, - -- goto previous line (_prevWord does not understand - -- zero w.cur) - w.cur = 1 - end - l.new, w.new = _prevWord(t, l.cur, w.cur) - end - - self.cursor:clear() - if w.new ~= 0 - and not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) - and self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then - -- word is in previous view - local pageno = self:prevView() - self:goto(pageno) - end - - -- update cursor - if w.cur == 0 then - -- meet line left end, must be handled as special case - if self:_isEntireWordInScreenRange(t[l.cur][1]) then - self:drawCursorBeforeWord(t, l.cur, 1) - end - else - if self:_isEntireWordInScreenRange(t[l.new][w.new]) then - self:drawCursorAfterWord(t, l.new, w.new) - end - end - elseif ev.code == KEY_FW_RIGHT then - if w.cur == 0 then - w.cur = 1 - w.new = 1 - else - l.new, w.new = _nextWord(t, l.cur, w.cur) - if w.new == 1 then - -- Must be come from the right end of previous line, - -- so goto the left end of current line. - w.cur = 0 - w.new = 0 - end - end - - self.cursor:clear() - - local tmp_w = w.new - if w.cur == 0 then - tmp_w = 1 - end - if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) - and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then - local pageno = self:nextView() - self:goto(pageno) - end - - if w.cur == 0 then - -- meet line left end, must be handled as special case - if self:_isEntireWordInScreenRange(t[l.new][1]) then - self:drawCursorBeforeWord(t, l.new, 1) - end - else - if self:_isEntireWordInScreenRange(t[l.new][w.new]) then - self:drawCursorAfterWord(t, l.new, w.new) - end - end - elseif ev.code == KEY_FW_UP then - if w.cur == 0 then - -- goto left end of last line - l.new = math.max(l.cur - 1, 1) - elseif l.cur == 1 and w.cur == 1 then - -- already first word, to the left end of first line - w.new = 0 - else - l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) - end - - self.cursor:clear() - - local tmp_w = w.new - if w.cur == 0 then - tmp_w = 1 - end - if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) - and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then - -- goto next view of current page - local pageno = self:prevView() - self:goto(pageno) - end - - if w.new == 0 then - if self:_isEntireWordInScreenRange(t[l.new][1]) then - self:drawCursorBeforeWord(t, l.new, 1) - end - else - if self:_isEntireWordInScreenRange(t[l.new][w.new]) then - self:drawCursorAfterWord(t, l.new, w.new) - end - end - elseif ev.code == KEY_FW_DOWN then - if w.cur == 0 then - -- on the left end of current line, - -- goto left end of next line - l.new = math.min(l.cur + 1, #t) - else - l.new, w.new = _wordInNextLine(t, l.cur, w.cur) - end - - self.cursor:clear() - - local tmp_w = w.new - if w.cur == 0 then - tmp_w = 1 - end - if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) - and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then - -- goto next view of current page - local pageno = self:nextView() - self:goto(pageno) - end - - if w.cur == 0 then - if self:_isEntireWordInScreenRange(t[l.new][1]) then - self:drawCursorBeforeWord(t, l.new, 1) - end - else - if self:_isEntireWordInScreenRange(t[l.new][w.new]) then - self:drawCursorAfterWord(t, l.new, w.new) - end - end - elseif ev.code == KEY_DEL then - if self.highlight[self.pageno] then - for k, text_item in ipairs(self.highlight[self.pageno]) do - for _, line_item in ipairs(text_item) do - if t[l.cur][w.cur].y0 >= line_item.y0 - and t[l.cur][w.cur].y1 <= line_item.y1 - and t[l.cur][w.cur].x0 >= line_item.x0 - and t[l.cur][w.cur].x1 <= line_item.x1 then - self.highlight[self.pageno][k] = nil - end - end -- EOF for line_item - end -- EOF for text_item - end -- EOF if not highlight table - if #self.highlight[self.pageno] == 0 then - self.highlight[self.pageno] = nil - end - return - elseif ev.code == KEY_FW_PRESS then - if w.cur == 0 then - w.cur = 1 - l.cur, w.cur = _prevWord(t, l.cur, w.cur) - end - l.new, w.new = l.cur, w.cur - l.start, w.start = l.cur, w.cur - running = false - self.cursor:clear() - elseif ev.code == KEY_BACK then - running = false - return - end -- EOF if key event - l.cur, w.cur = l.new, w.new - fb:refresh(1) - end - end -- EOF while - --print("start", l.cur, w.cur, l.start, w.start) - - -- two helper functions for highlight - local function _togglePrevWordHighLight(t, l, w) - l.new, w.new = _prevWord(t, l.cur, w.cur) - - if l.cur == 1 and w.cur == 1 then - is_meet_start = true - -- left end of first line must be handled as special case - w.new = 0 - end - - if w.new ~= 0 and - not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then - -- word out of left and right sides of current view should - -- not trigger pan by page - if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then - -- word is in previous view - local pageno = self:prevView() - self:goto(pageno) - end - - local l0 = l.start - local w0 = w.start - local l1 = l.cur - local w1 = w.cur - if _isMovingForward(l, w) then - l0, w0 = _nextWord(t, l0, w0) - l1, w1 = l.new, w.new - end - self:_toggleTextHighLight(t, l0, w0, - l1, w1) - else - self:_toggleWordHighLight(t, l.cur, w.cur) - end - - l.cur, w.cur = l.new, w.new - return l, w, (is_meet_start or false) - end - - local function _toggleNextWordHighLight(t, l, w) - if w.cur == 0 then - w.new = 1 - else - l.new, w.new = _nextWord(t, l.cur, w.cur) - end - if l.new == #t and w.new == #t[#t] then - is_meet_end = true - end - - if not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then - if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then - local pageno = self:nextView() - self:goto(pageno) - end - - local tmp_l = l.start - local tmp_w = w.start - if _isMovingForward(l, w) then - tmp_l, tmp_w = _nextWord(t, tmp_l, tmp_w) - end - self:_toggleTextHighLight(t, tmp_l, tmp_w, - l.new, w.new) - else - self:_toggleWordHighLight(t, l.new, w.new) - end - - l.cur, w.cur = l.new, w.new - return l, w, (is_meet_end or false) - end - - - -- go into highlight mode - running = true - while running do - local ev = input.waitForEvent() - ev.code = adjustKeyEvents(ev) - if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - if ev.code == KEY_FW_LEFT then - is_meet_end = false - if not is_meet_start then - l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) - end - elseif ev.code == KEY_FW_RIGHT then - is_meet_start = false - if not is_meet_end then - l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) - end -- EOF if is not is_meet_end - elseif ev.code == KEY_FW_UP then - is_meet_end = false - if not is_meet_start then - if l.cur == 1 then - -- handle left end of first line as special case - tmp_l = 1 - tmp_w = 0 - else - tmp_l, tmp_w = _wordInPrevLine(t, l.cur, w.cur) - end - while not (tmp_l == l.cur and tmp_w == w.cur) do - l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) - end - end - elseif ev.code == KEY_FW_DOWN then - is_meet_start = false - if not is_meet_end then - if w.cur == 0 then - -- handle left end of first line as special case - tmp_l = math.min(tmp_l + 1, #t) - tmp_w = 1 - else - tmp_l, tmp_w = _wordInNextLine(t, l.new, w.new) - end - while not (tmp_l == l.cur and tmp_w == w.cur) do - l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) - end - end - elseif ev.code == KEY_FW_PRESS then - local l0, w0, l1, w1 - - -- find start and end of highlight text - if _isMovingForward(l, w) then - l0, w0 = _nextWord(t, l.start, w.start) - l1, w1 = l.cur, w.cur - else - l0, w0 = _nextWord(t, l.cur, w.cur) - l1, w1 = l.start, w.start - end - -- remove selection area - self:_toggleTextHighLight(t, l0, w0, l1, w1) - - -- put text into highlight table of current page - local hl_item = {} - local s = "" - local prev_l = l0 - local prev_w = w0 - local l_item = { - x0 = t[l0][w0].x0, - y0 = t[l0].y0, - y1 = t[l0].y1, - } - for _l,_w in self:_wordIterFromRange(t, l0, w0, l1, w1) do - local word_item = t[_l][_w] - if _l > prev_l then - -- in next line, add previous line to highlight item - l_item.x1 = t[prev_l][prev_w].x1 - table.insert(hl_item, l_item) - -- re initialize l_item for new line - l_item = { - x0 = word_item.x0, - y0 = t[_l].y0, - y1 = t[_l].y1, - } - end - s = s .. word_item.word .. " " - prev_l, prev_w = _l, _w - end - -- insert last line of text in line item - l_item.x1 = t[prev_l][prev_w].x1 - table.insert(hl_item, l_item) - hl_item.text = s - - if not self.highlight[self.pageno] then - self.highlight[self.pageno] = {} - end - table.insert(self.highlight[self.pageno], hl_item) - - running = false - elseif ev.code == KEY_BACK then - running = false - end -- EOF if key event - fb:refresh(1) - end - end -- EOF while +function DJVUReader:getText(pageno) + return self.doc:getPageText(pageno) end - diff --git a/pdfreader.lua b/pdfreader.lua index 334a69d85..f998d4349 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -30,3 +30,25 @@ function PDFReader:open(filename) end return true end + +-----------[ highlight support ]---------- + +function PDFReader:rectCoordTransform(x0, y0, x1, y1) + return + x0 * self.globalzoom, + y1 * self.globalzoom - self.offset_y, + x1 - x0, + y1 - y0 +end + +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(dump(text)) + page:close() + return text +end diff --git a/unireader.lua b/unireader.lua index 8b9e0887f..89ac81773 100644 --- a/unireader.lua +++ b/unireader.lua @@ -112,23 +112,620 @@ function UniReader:init() self:addAllCommands() end +-----------[ highlight support ]---------- + ---------------------------------------------------- --- You need to overwrite following two methods if your --- reader supports highlight feature. +-- Given coordinates of four conners and return +-- coordinate of upper left conner with with and height ---------------------------------------------------- +function UniReader:rectCoordTransform(x0, y0, x1, y1) + return + x0 * self.globalzoom, + y1 * self.globalzoom - self.offset_y, + (x1 - x0) * self.globalzoom, + (y1 - y0) * self.globalzoom +end -function UniReader:startHighLightMode() - return +-- make sure the whole word can be seen in screen +function UniReader:_isEntireWordInScreenRange(w) + return self:_isEntireWordInScreenHeightRange(w) and + self:_isEntireWordInScreenWidthRange(w) end -function UniReader:highLightText() - return +-- y axel in djvulibre starts from bottom +function UniReader:_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 + +function UniReader:_isEntireWordInScreenWidthRange(w) + return (w ~= nil) and + (w.x0 * self.globalzoom >= -self.offset_x) and + (w.x1 * self.globalzoom <= -self.offset_x + G_width) +end + +-- make sure at least part of the word can be seen in screen +function UniReader:_isWordInScreenRange(w) + return (w ~= nil) and + (self.cur_full_height - (w.y0 * self.globalzoom) >= + -self.offset_y) and + (self.cur_full_height - (w.y1 * self.globalzoom) <= + -self.offset_y + G_height) and + (w.x1 * self.globalzoom >= -self.offset_x) and + (w.x0 * self.globalzoom <= -self.offset_x + G_width) end function UniReader:toggleTextHighLight(word_list) - return + for _,text_item in ipairs(word_list) do + for _,line_item in ipairs(text_item) do + -- make sure that line is in screen range + if self:_isEntireWordInScreenHeightRange(line_item) then + local x, y, w, h = self:rectCoordTransform( + line_item.x0, line_item.y0, + line_item.x1, line_item.y1) + -- slightly enlarge the highlight height + -- for better viewing experience + x = x + y = y - h * 0.1 + w = w + h = h * 1.2 + + self.highlight.drawer = self.highlight.drawer or "underscore" + if self.highlight.drawer == "underscore" then + self.highlight.line_width = self.highlight.line_width or 2 + self.highlight.line_color = self.highlight.line_color or 5 + fb.bb:paintRect(x, y+h-1, w, + self.highlight.line_width, + self.highlight.line_color) + elseif self.highlight.drawer == "marker" then + fb.bb:invertRect(x, y, w, h) + end + end -- EOF if isEntireWordInScreenHeightRange + end -- EOF for line_item + end -- EOF for text_item +end + +function UniReader:_wordIterFromRange(t, l0, w0, l1, w1) + local i = l0 + local j = w0 - 1 + return function() + if i <= l1 then + -- if in line range, loop through lines + if i == l1 then + -- in last line + if j < w1 then + j = j + 1 + else + -- out of range return nil + return nil, nil + end + else + if j < #t[i] then + j = j + 1 + else + -- goto next line + i = i + 1 + j = 1 + end + end + return i, j + end + end -- EOF closure +end + +function UniReader:_toggleWordHighLight(t, l, w) + x, y, w, h = self:rectCoordTransform(t[l][w].x0, t[l].y0, + t[l][w].x1, t[l].y1) + -- slightly enlarge the highlight range for better viewing experience + x = x - w * 0.05 + y = y - h * 0.05 + w = w * 1.1 + h = h * 1.1 + + fb.bb:invertRect(x, y, w, h) end +function UniReader:_toggleTextHighLight(t, l0, w0, l1, w1) + --print("# toggle range", l0, w0, l1, w1) + -- make sure (l0, w0) is smaller than (l1, w1) + if l0 > l1 then + l0, l1 = l1, l0 + w0, w1 = w1, w0 + elseif l0 == l1 and w0 > w1 then + w0, w1 = w1, w0 + end + + for _l, _w in self:_wordIterFromRange(t, l0, w0, l1, w1) do + if self:_isWordInScreenRange(t[_l][_w]) then + -- blitbuffer module will take care of the out of screen range part. + self:_toggleWordHighLight(t, _l, _w) + end + end +end + +-- remember to clear cursor before calling this +function UniReader:drawCursorAfterWord(t, l, w) + local x, y, w, h = self:rectCoordTransform(t[l].x0, t[l].y0, t[l].x1, t[l].y1) + self.cursor:setHeight(h) + self.cursor:moveTo(x+w, y) + self.cursor:draw() +end + +function UniReader:drawCursorBeforeWord(t, l, w) + local x, y, w, h = self:rectCoordTransform(t[l].x0, t[l].y0, t[l].x1, t[l].y1) + self.cursor:setHeight(h) + self.cursor:moveTo(x, y) + self.cursor:draw() +end + +function UniReader:getText(pageno) + -- define a sensible implementation when your reader supports it + return nil +end + +function UniReader:startHighLightMode() + local t = self:getText(self.pageno) + if not t or #t == 0 then + return nil + end + + local function _findFirstWordInView(t) + for i=1, #t, 1 do + if self:_isEntireWordInScreenRange(t[i][1]) then + return i, 1 + end + end + + return nil + end + + local function _prevWord(t, cur_l, cur_w) + if cur_l == 1 then + if cur_w == 1 then + -- already the first word + return 1, 1 + else + -- in first line, but not first word + return cur_l, cur_w -1 + end + end + + if cur_w <= 1 then + -- first word in current line, goto previous line + return cur_l - 1, #t[cur_l-1] + else + return cur_l, cur_w - 1 + end + end + + local function _nextWord(t, cur_l, cur_w) + if cur_l == #t then + if cur_w == #(t[cur_l]) then + -- already the last word + return cur_l, cur_w + else + -- in last line, but not last word + return cur_l, cur_w + 1 + end + end + + if cur_w < #t[cur_l] then + return cur_l, cur_w + 1 + else + -- last word in current line, move to next line + return cur_l + 1, 1 + end + end + + local function _wordInNextLine(t, cur_l, cur_w) + if cur_l == #t then + -- already in last line, return the last word + return cur_l, #(t[cur_l]) + else + return cur_l + 1, math.min(cur_w, #t[cur_l+1]) + end + end + + local function _wordInPrevLine(t, cur_l, cur_w) + if cur_l == 1 then + -- already in first line, return the first word + return 1, 1 + else + return cur_l - 1, math.min(cur_w, #t[cur_l-1]) + end + end + + local function _isMovingForward(l, w) + return l.cur > l.start or (l.cur == l.start and w.cur > w.start) + end + + local l = {} + local w = {} + + l.start, w.start = _findFirstWordInView(t) + if not l.start then + print("# no text in current view!") + return + end + + l.cur, w.cur = l.start, w.start + l.new, w.new = l.cur, w.cur + local is_meet_start = false + local is_meet_end = false + local running = true + + local cx, cy, cw, ch = self:rectCoordTransform( + t[l.cur][w.cur].x0, + t[l.cur][w.cur].y0, + t[l.cur][w.cur].x1, + t[l.cur][w.cur].y1) + + self.cursor = Cursor:new { + x_pos = cx+cw, + y_pos = cy, + h = ch, + line_width_factor = 4, + } + self.cursor:draw() + fb:refresh(1) + + -- first use cursor to place start pos for highlight + while running do + local ev = input.waitForEvent() + ev.code = adjustKeyEvents(ev) + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + if ev.code == KEY_FW_LEFT then + if w.cur == 1 then + w.cur = 0 + w.new = 0 + else + if w.cur == 0 then + -- already at the left end of current line, + -- goto previous line (_prevWord does not understand + -- zero w.cur) + w.cur = 1 + end + l.new, w.new = _prevWord(t, l.cur, w.cur) + end + + self.cursor:clear() + if w.new ~= 0 + and not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) + and self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then + -- word is in previous view + local pageno = self:prevView() + self:goto(pageno) + end + + -- update cursor + if w.cur == 0 then + -- meet line left end, must be handled as special case + if self:_isEntireWordInScreenRange(t[l.cur][1]) then + self:drawCursorBeforeWord(t, l.cur, 1) + end + else + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end + end + elseif ev.code == KEY_FW_RIGHT then + if w.cur == 0 then + w.cur = 1 + w.new = 1 + else + l.new, w.new = _nextWord(t, l.cur, w.cur) + if w.new == 1 then + -- Must be come from the right end of previous line, + -- so goto the left end of current line. + w.cur = 0 + w.new = 0 + end + end + + self.cursor:clear() + + local tmp_w = w.new + if w.cur == 0 then + tmp_w = 1 + end + if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then + local pageno = self:nextView() + self:goto(pageno) + end + + if w.cur == 0 then + -- meet line left end, must be handled as special case + if self:_isEntireWordInScreenRange(t[l.new][1]) then + self:drawCursorBeforeWord(t, l.new, 1) + end + else + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end + end + elseif ev.code == KEY_FW_UP then + if w.cur == 0 then + -- goto left end of last line + l.new = math.max(l.cur - 1, 1) + elseif l.cur == 1 and w.cur == 1 then + -- already first word, to the left end of first line + w.new = 0 + else + l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) + end + + self.cursor:clear() + + local tmp_w = w.new + if w.cur == 0 then + tmp_w = 1 + end + if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then + -- goto next view of current page + local pageno = self:prevView() + self:goto(pageno) + end + + if w.new == 0 then + if self:_isEntireWordInScreenRange(t[l.new][1]) then + self:drawCursorBeforeWord(t, l.new, 1) + end + else + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end + end + elseif ev.code == KEY_FW_DOWN then + if w.cur == 0 then + -- on the left end of current line, + -- goto left end of next line + l.new = math.min(l.cur + 1, #t) + else + l.new, w.new = _wordInNextLine(t, l.cur, w.cur) + end + + self.cursor:clear() + + local tmp_w = w.new + if w.cur == 0 then + tmp_w = 1 + end + if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then + -- goto next view of current page + local pageno = self:nextView() + self:goto(pageno) + end + + if w.cur == 0 then + if self:_isEntireWordInScreenRange(t[l.new][1]) then + self:drawCursorBeforeWord(t, l.new, 1) + end + else + if self:_isEntireWordInScreenRange(t[l.new][w.new]) then + self:drawCursorAfterWord(t, l.new, w.new) + end + end + elseif ev.code == KEY_DEL then + if self.highlight[self.pageno] then + for k, text_item in ipairs(self.highlight[self.pageno]) do + for _, line_item in ipairs(text_item) do + if t[l.cur][w.cur].y0 >= line_item.y0 + and t[l.cur][w.cur].y1 <= line_item.y1 + and t[l.cur][w.cur].x0 >= line_item.x0 + and t[l.cur][w.cur].x1 <= line_item.x1 then + self.highlight[self.pageno][k] = nil + end + end -- EOF for line_item + end -- EOF for text_item + end -- EOF if not highlight table + if #self.highlight[self.pageno] == 0 then + self.highlight[self.pageno] = nil + end + return + elseif ev.code == KEY_FW_PRESS then + if w.cur == 0 then + w.cur = 1 + l.cur, w.cur = _prevWord(t, l.cur, w.cur) + end + l.new, w.new = l.cur, w.cur + l.start, w.start = l.cur, w.cur + running = false + self.cursor:clear() + elseif ev.code == KEY_BACK then + running = false + return + end -- EOF if key event + l.cur, w.cur = l.new, w.new + fb:refresh(1) + end + end -- EOF while + --print("start", l.cur, w.cur, l.start, w.start) + + -- two helper functions for highlight + local function _togglePrevWordHighLight(t, l, w) + l.new, w.new = _prevWord(t, l.cur, w.cur) + + if l.cur == 1 and w.cur == 1 then + is_meet_start = true + -- left end of first line must be handled as special case + w.new = 0 + end + + if w.new ~= 0 and + not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then + -- word out of left and right sides of current view should + -- not trigger pan by page + if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then + -- word is in previous view + local pageno = self:prevView() + self:goto(pageno) + end + + local l0 = l.start + local w0 = w.start + local l1 = l.cur + local w1 = w.cur + if _isMovingForward(l, w) then + l0, w0 = _nextWord(t, l0, w0) + l1, w1 = l.new, w.new + end + self:_toggleTextHighLight(t, l0, w0, + l1, w1) + else + self:_toggleWordHighLight(t, l.cur, w.cur) + end + + l.cur, w.cur = l.new, w.new + return l, w, (is_meet_start or false) + end + + local function _toggleNextWordHighLight(t, l, w) + if w.cur == 0 then + w.new = 1 + else + l.new, w.new = _nextWord(t, l.cur, w.cur) + end + if l.new == #t and w.new == #t[#t] then + is_meet_end = true + end + + if not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then + if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then + local pageno = self:nextView() + self:goto(pageno) + end + + local tmp_l = l.start + local tmp_w = w.start + if _isMovingForward(l, w) then + tmp_l, tmp_w = _nextWord(t, tmp_l, tmp_w) + end + self:_toggleTextHighLight(t, tmp_l, tmp_w, + l.new, w.new) + else + self:_toggleWordHighLight(t, l.new, w.new) + end + + l.cur, w.cur = l.new, w.new + return l, w, (is_meet_end or false) + end + + + -- go into highlight mode + running = true + while running do + local ev = input.waitForEvent() + ev.code = adjustKeyEvents(ev) + if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then + if ev.code == KEY_FW_LEFT then + is_meet_end = false + if not is_meet_start then + l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) + end + elseif ev.code == KEY_FW_RIGHT then + is_meet_start = false + if not is_meet_end then + l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) + end -- EOF if is not is_meet_end + elseif ev.code == KEY_FW_UP then + is_meet_end = false + if not is_meet_start then + if l.cur == 1 then + -- handle left end of first line as special case + tmp_l = 1 + tmp_w = 0 + else + tmp_l, tmp_w = _wordInPrevLine(t, l.cur, w.cur) + end + while not (tmp_l == l.cur and tmp_w == w.cur) do + l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) + end + end + elseif ev.code == KEY_FW_DOWN then + is_meet_start = false + if not is_meet_end then + if w.cur == 0 then + -- handle left end of first line as special case + tmp_l = math.min(tmp_l + 1, #t) + tmp_w = 1 + else + tmp_l, tmp_w = _wordInNextLine(t, l.new, w.new) + end + while not (tmp_l == l.cur and tmp_w == w.cur) do + l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) + end + end + elseif ev.code == KEY_FW_PRESS then + local l0, w0, l1, w1 + + -- find start and end of highlight text + if _isMovingForward(l, w) then + l0, w0 = _nextWord(t, l.start, w.start) + l1, w1 = l.cur, w.cur + else + l0, w0 = _nextWord(t, l.cur, w.cur) + l1, w1 = l.start, w.start + end + -- remove selection area + self:_toggleTextHighLight(t, l0, w0, l1, w1) + + -- put text into highlight table of current page + local hl_item = {} + local s = "" + local prev_l = l0 + local prev_w = w0 + local l_item = { + x0 = t[l0][w0].x0, + y0 = t[l0].y0, + y1 = t[l0].y1, + } + for _l,_w in self:_wordIterFromRange(t, l0, w0, l1, w1) do + local word_item = t[_l][_w] + if _l > prev_l then + -- in next line, add previous line to highlight item + l_item.x1 = t[prev_l][prev_w].x1 + table.insert(hl_item, l_item) + -- re initialize l_item for new line + l_item = { + x0 = word_item.x0, + y0 = t[_l].y0, + y1 = t[_l].y1, + } + end + s = s .. word_item.word .. " " + prev_l, prev_w = _l, _w + end + -- insert last line of text in line item + l_item.x1 = t[prev_l][prev_w].x1 + table.insert(hl_item, l_item) + hl_item.text = s + + if not self.highlight[self.pageno] then + self.highlight[self.pageno] = {} + end + table.insert(self.highlight[self.pageno], hl_item) + + running = false + elseif ev.code == KEY_BACK then + running = false + end -- EOF if key event + fb:refresh(1) + end + end -- EOF while +end + + + + + + + ---------------------------------------------------- -- Renderer memory ---------------------------------------------------- @@ -1289,5 +1886,20 @@ function UniReader:addAllCommands() end end) -- end panning + -- highlight mode + self.commands:add(KEY_N, nil, "N", + "start highlight mode", + function(unireader) + unireader:startHighLightMode() + unireader:goto(unireader.pageno) + end + ) + self.commands:add(KEY_N, MOD_SHIFT, "N", + "display all highlights", + function(unireader) + unireader:showHighLight() + unireader:goto(unireader.pageno) + end + ) print("## defined commands "..dump(self.commands.map)) end From face8b44c0c6036c854237905d651cfd3ded4693 Mon Sep 17 00:00:00 2001 From: HW Date: Wed, 11 Apr 2012 23:33:47 +0200 Subject: [PATCH 146/183] fixed cursor positioning --- unireader.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/unireader.lua b/unireader.lua index 89ac81773..9ae8470ac 100644 --- a/unireader.lua +++ b/unireader.lua @@ -248,14 +248,16 @@ end -- remember to clear cursor before calling this function UniReader:drawCursorAfterWord(t, l, w) - local x, y, w, h = self:rectCoordTransform(t[l].x0, t[l].y0, t[l].x1, t[l].y1) + local _, _, _, h = self:rectCoordTransform(0, t[l].y0, 0, t[l].y1) + local x, y, wd, h = self:rectCoordTransform(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) self.cursor:setHeight(h) - self.cursor:moveTo(x+w, y) + self.cursor:moveTo(x+wd, y) self.cursor:draw() end function UniReader:drawCursorBeforeWord(t, l, w) - local x, y, w, h = self:rectCoordTransform(t[l].x0, t[l].y0, t[l].x1, t[l].y1) + local _, _, _, h = self:rectCoordTransform(0, t[l].y0, 0, t[l].y1) + local x, y, _, h = self:rectCoordTransform(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) self.cursor:setHeight(h) self.cursor:moveTo(x, y) self.cursor:draw() From 56d4700001da79c8a273ea15e9ac035b817b4248 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 12 Apr 2012 11:23:22 +0800 Subject: [PATCH 147/183] mod: changes APIs in highlighting code --- djvureader.lua | 42 ++++++++++++++++++++------- pdfreader.lua | 13 ++------- unireader.lua | 79 ++++++++++++++++++++++++++++++-------------------- 3 files changed, 82 insertions(+), 52 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 38efbedd8..dd1892103 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -24,24 +24,44 @@ function DJVUReader:adjustDjvuReaderCommand() end ------------[ highlight support ]---------- +---------------------------------------------------- +-- highlight support +---------------------------------------------------- +function DJVUReader:getText(pageno) + return self.doc:getPageText(pageno) +end ---------------------------------------------------- --- Given coordinates of four conners and return --- coordinate of upper left conner with with and height --- -- In djvulibre library, some coordinates starts from --- down left conner, i.e. y is upside down. This method --- only transform these coordinates. +-- lower left conner, i.e. y is upside down in kpv's +-- coordinate. So y0 should be taken with special care. ---------------------------------------------------- -function DJVUReader:rectCoordTransform(x0, y0, x1, y1) +function DJVUReader:zoomedRectCoordTransform(x0, y0, x1, y1) return - self.offset_x + x0 * self.globalzoom, - self.offset_y + self.cur_full_height - (y1 * self.globalzoom), + x0 * self.globalzoom, + self.cur_full_height - (y1 * self.globalzoom), (x1 - x0) * self.globalzoom, (y1 - y0) * self.globalzoom end -function DJVUReader:getText(pageno) - return self.doc:getPageText(pageno) +-- 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:_isWordInScreenRange(w) + return (w ~= nil) and + (self.cur_full_height - (w.y0 * self.globalzoom) >= + -self.offset_y) and + (self.cur_full_height - (w.y1 * self.globalzoom) <= + -self.offset_y + G_height) and + (w.x1 * self.globalzoom >= -self.offset_x) and + (w.x0 * self.globalzoom <= -self.offset_x + G_width) end + + diff --git a/pdfreader.lua b/pdfreader.lua index f998d4349..c6d55d9ad 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -31,16 +31,9 @@ function PDFReader:open(filename) return true end ------------[ highlight support ]---------- - -function PDFReader:rectCoordTransform(x0, y0, x1, y1) - return - x0 * self.globalzoom, - y1 * self.globalzoom - self.offset_y, - x1 - x0, - y1 - y0 -end - +---------------------------------------------------- +-- highlight support +---------------------------------------------------- function PDFReader:getText(pageno) local ok, page = pcall(self.doc.openPage, self.doc, pageno) if not ok then diff --git a/unireader.lua b/unireader.lua index 9ae8470ac..dcb229dab 100644 --- a/unireader.lua +++ b/unireader.lua @@ -112,20 +112,42 @@ function UniReader:init() self:addAllCommands() end ------------[ highlight support ]---------- +---------------------------------------------------- +-- highlight support +---------------------------------------------------- ---------------------------------------------------- --- Given coordinates of four conners and return --- coordinate of upper left conner with with and height +-- Given coordinates of four conners in oringinal page +-- size and return coordinate of upper left conner in +-- zoomed page size with width and height. ---------------------------------------------------- -function UniReader:rectCoordTransform(x0, y0, x1, y1) +function UniReader:zoomedRectCoordTransform(x0, y0, x1, y1) return x0 * self.globalzoom, - y1 * self.globalzoom - self.offset_y, + y0 * self.globalzoom, (x1 - x0) * self.globalzoom, (y1 - y0) * self.globalzoom end +---------------------------------------------------- +-- Given coordinates of four conners in oringinal page +-- size and return Rectangular area in screen. You +-- might want to call this when you want to draw stuff +-- on screen. +-- +-- NOTE: this method doese not check whether given area +-- is can be shown in current screen. Make sure to check +-- with _isEntireWordInScreenRange() before you want to +-- draw on screen. +---------------------------------------------------- +function UniReader:getRectInScreen(x0, y0, x1, y1) + x, y, w, h = self:zoomedRectCoordTransform(x0, y0, x1, y1) + return + x + self.offset_x, + y + self.offset_y, + w, h +end + -- make sure the whole word can be seen in screen function UniReader:_isEntireWordInScreenRange(w) return self:_isEntireWordInScreenHeightRange(w) and @@ -135,10 +157,8 @@ end -- y axel in djvulibre starts from bottom function UniReader:_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) + (w.y1 * self.globalzoom) >= -self.offset_y + and (w.y0 * self.globalzoom) <= -self.offset_y + G_height end function UniReader:_isEntireWordInScreenWidthRange(w) @@ -150,12 +170,10 @@ end -- make sure at least part of the word can be seen in screen function UniReader:_isWordInScreenRange(w) return (w ~= nil) and - (self.cur_full_height - (w.y0 * self.globalzoom) >= - -self.offset_y) and - (self.cur_full_height - (w.y1 * self.globalzoom) <= - -self.offset_y + G_height) and - (w.x1 * self.globalzoom >= -self.offset_x) and - (w.x0 * self.globalzoom <= -self.offset_x + G_width) + (w.y0 * self.globalzoom) >= -self.offset_y + and w.y1 * self.globalzoom <= -self.offset_y + G_height + and w.x1 * self.globalzoom >= -self.offset_x + and w.x0 * self.globalzoom <= -self.offset_x + G_width end function UniReader:toggleTextHighLight(word_list) @@ -163,7 +181,7 @@ function UniReader:toggleTextHighLight(word_list) for _,line_item in ipairs(text_item) do -- make sure that line is in screen range if self:_isEntireWordInScreenHeightRange(line_item) then - local x, y, w, h = self:rectCoordTransform( + local x, y, w, h = self:getRectInScreen( line_item.x0, line_item.y0, line_item.x1, line_item.y1) -- slightly enlarge the highlight height @@ -183,9 +201,9 @@ function UniReader:toggleTextHighLight(word_list) elseif self.highlight.drawer == "marker" then fb.bb:invertRect(x, y, w, h) end - end -- EOF if isEntireWordInScreenHeightRange - end -- EOF for line_item - end -- EOF for text_item + end -- if isEntireWordInScreenHeightRange + end -- for line_item + end -- for text_item end function UniReader:_wordIterFromRange(t, l0, w0, l1, w1) @@ -217,7 +235,7 @@ function UniReader:_wordIterFromRange(t, l0, w0, l1, w1) end function UniReader:_toggleWordHighLight(t, l, w) - x, y, w, h = self:rectCoordTransform(t[l][w].x0, t[l].y0, + x, y, w, h = self:getRectInScreen(t[l][w].x0, t[l].y0, t[l][w].x1, t[l].y1) -- slightly enlarge the highlight range for better viewing experience x = x - w * 0.05 @@ -248,16 +266,20 @@ end -- remember to clear cursor before calling this function UniReader:drawCursorAfterWord(t, l, w) - local _, _, _, h = self:rectCoordTransform(0, t[l].y0, 0, t[l].y1) - local x, y, wd, h = self:rectCoordTransform(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) + -- get height of line t[l][w] is in + local _, _, _, h = self:zoomedRectCoordTransform(0, t[l].y0, 0, t[l].y1) + -- get rect of t[l][w] + local x, y, wd, h = self:getRectInScreen(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) self.cursor:setHeight(h) self.cursor:moveTo(x+wd, y) self.cursor:draw() end function UniReader:drawCursorBeforeWord(t, l, w) - local _, _, _, h = self:rectCoordTransform(0, t[l].y0, 0, t[l].y1) - local x, y, _, h = self:rectCoordTransform(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) + -- get height of line t[l][w] is in + local _, _, _, h = self:zoomedRectCoordTransform(0, t[l].y0, 0, t[l].y1) + -- get rect of t[l][w] + local x, y, _, h = self:getRectInScreen(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) self.cursor:setHeight(h) self.cursor:moveTo(x, y) self.cursor:draw() @@ -359,7 +381,7 @@ function UniReader:startHighLightMode() local is_meet_end = false local running = true - local cx, cy, cw, ch = self:rectCoordTransform( + local cx, cy, cw, ch = self:getRectInScreen( t[l.cur][w.cur].x0, t[l.cur][w.cur].y0, t[l.cur][w.cur].x1, @@ -716,18 +738,13 @@ function UniReader:startHighLightMode() running = false elseif ev.code == KEY_BACK then running = false - end -- EOF if key event + end -- if key event fb:refresh(1) end end -- EOF while end - - - - - ---------------------------------------------------- -- Renderer memory ---------------------------------------------------- From 66ab3a2f56f60104ddf16a22c60115a97e5b6dbc Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 12 Apr 2012 11:34:24 +0800 Subject: [PATCH 148/183] fix: typo in unireader's comment --- unireader.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/unireader.lua b/unireader.lua index dcb229dab..d634ecb28 100644 --- a/unireader.lua +++ b/unireader.lua @@ -117,7 +117,7 @@ end ---------------------------------------------------- ---------------------------------------------------- --- Given coordinates of four conners in oringinal page +-- Given coordinates of four corners in original page -- size and return coordinate of upper left conner in -- zoomed page size with width and height. ---------------------------------------------------- @@ -130,15 +130,15 @@ function UniReader:zoomedRectCoordTransform(x0, y0, x1, y1) end ---------------------------------------------------- --- Given coordinates of four conners in oringinal page --- size and return Rectangular area in screen. You +-- Given coordinates of four corners in original page +-- size and return rectangular area in screen. You -- might want to call this when you want to draw stuff -- on screen. -- --- NOTE: this method doese not check whether given area +-- NOTE: this method does not check whether given area -- is can be shown in current screen. Make sure to check -- with _isEntireWordInScreenRange() before you want to --- draw on screen. +-- draw on the returned area. ---------------------------------------------------- function UniReader:getRectInScreen(x0, y0, x1, y1) x, y, w, h = self:zoomedRectCoordTransform(x0, y0, x1, y1) @@ -154,7 +154,6 @@ function UniReader:_isEntireWordInScreenRange(w) self:_isEntireWordInScreenWidthRange(w) end --- y axel in djvulibre starts from bottom function UniReader:_isEntireWordInScreenHeightRange(w) return (w ~= nil) and (w.y1 * self.globalzoom) >= -self.offset_y @@ -231,7 +230,7 @@ function UniReader:_wordIterFromRange(t, l0, w0, l1, w1) end return i, j end - end -- EOF closure + end -- closure end function UniReader:_toggleWordHighLight(t, l, w) From 75d5b5f98439c8dbb3415a0536e7273f3184b68d Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 12 Apr 2012 13:43:20 +0800 Subject: [PATCH 149/183] fix: three bugs in highlight mode * handle left end of first line in cursor move * properly highlight first word if cursor starts from left end of first line * handle right end of last line in cursor move --- unireader.lua | 62 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/unireader.lua b/unireader.lua index d634ecb28..d06f2b1d9 100644 --- a/unireader.lua +++ b/unireader.lua @@ -400,16 +400,23 @@ function UniReader:startHighLightMode() local ev = input.waitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then - if ev.code == KEY_FW_LEFT then + if ev.code == KEY_FW_LEFT and not is_meet_start then + is_meet_end = false if w.cur == 1 then + -- goto left end of current line. _prevWord does not + -- understand and will not return zero w.cur + -- because zero does not point to a word, so we need to + -- handle left end manually here. w.cur = 0 w.new = 0 + if l.cur == 1 then + -- on left end of first line + is_meet_start = true + end else - if w.cur == 0 then - -- already at the left end of current line, - -- goto previous line (_prevWord does not understand - -- zero w.cur) - w.cur = 1 + -- handle left end manually. + if w.cur == 0 then + w.cur = 1 end l.new, w.new = _prevWord(t, l.cur, w.cur) end @@ -434,7 +441,9 @@ function UniReader:startHighLightMode() self:drawCursorAfterWord(t, l.new, w.new) end end - elseif ev.code == KEY_FW_RIGHT then + elseif ev.code == KEY_FW_RIGHT and not is_meet_end then + print(dump(l),dump(w)) + is_meet_start = false if w.cur == 0 then w.cur = 1 w.new = 1 @@ -447,6 +456,9 @@ function UniReader:startHighLightMode() w.new = 0 end end + if l.new == #t and w.new == #t[l.new] then + is_meet_end = true + end self.cursor:clear() @@ -470,13 +482,15 @@ function UniReader:startHighLightMode() self:drawCursorAfterWord(t, l.new, w.new) end end - elseif ev.code == KEY_FW_UP then + elseif ev.code == KEY_FW_UP and not is_meet_start then + is_meet_end = false if w.cur == 0 then -- goto left end of last line l.new = math.max(l.cur - 1, 1) elseif l.cur == 1 and w.cur == 1 then -- already first word, to the left end of first line w.new = 0 + is_meet_start = true else l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) end @@ -503,7 +517,8 @@ function UniReader:startHighLightMode() self:drawCursorAfterWord(t, l.new, w.new) end end - elseif ev.code == KEY_FW_DOWN then + elseif ev.code == KEY_FW_DOWN and not is_meet_end then + is_meet_start = false if w.cur == 0 then -- on the left end of current line, -- goto left end of next line @@ -511,6 +526,9 @@ function UniReader:startHighLightMode() else l.new, w.new = _wordInNextLine(t, l.cur, w.cur) end + if l.new == #t and w.new == #t[l.new] then + is_meet_end = true + end self.cursor:clear() @@ -544,18 +562,14 @@ function UniReader:startHighLightMode() and t[l.cur][w.cur].x1 <= line_item.x1 then self.highlight[self.pageno][k] = nil end - end -- EOF for line_item - end -- EOF for text_item - end -- EOF if not highlight table + end -- for line_item + end -- for text_item + end -- if not highlight table if #self.highlight[self.pageno] == 0 then self.highlight[self.pageno] = nil end return elseif ev.code == KEY_FW_PRESS then - if w.cur == 0 then - w.cur = 1 - l.cur, w.cur = _prevWord(t, l.cur, w.cur) - end l.new, w.new = l.cur, w.cur l.start, w.start = l.cur, w.cur running = false @@ -563,15 +577,23 @@ function UniReader:startHighLightMode() elseif ev.code == KEY_BACK then running = false return - end -- EOF if key event + end -- if check key event l.cur, w.cur = l.new, w.new fb:refresh(1) end - end -- EOF while + end -- while running --print("start", l.cur, w.cur, l.start, w.start) -- two helper functions for highlight local function _togglePrevWordHighLight(t, l, w) + if w.cur == 0 then + if l.cur == 1 then + -- already at the begin of first line, nothing to toggle + return + else + w.cur = 1 + end + end l.new, w.new = _prevWord(t, l.cur, w.cur) if l.cur == 1 and w.cur == 1 then @@ -655,7 +677,7 @@ function UniReader:startHighLightMode() is_meet_start = false if not is_meet_end then l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) - end -- EOF if is not is_meet_end + end -- if not is_meet_end elseif ev.code == KEY_FW_UP then is_meet_end = false if not is_meet_start then @@ -669,7 +691,7 @@ function UniReader:startHighLightMode() while not (tmp_l == l.cur and tmp_w == w.cur) do l, w, is_meet_start = _togglePrevWordHighLight(t, l, w) end - end + end -- not is_meet_start elseif ev.code == KEY_FW_DOWN then is_meet_start = false if not is_meet_end then From 3438d6749d8e0bf78c253890cc411af506cc7b62 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 12 Apr 2012 15:50:19 +0800 Subject: [PATCH 150/183] mod: rewrite part of highlight code * add _isEntireLineInScreenHeightRange() method For better page view navigation when highlighting or moving cursor. * bug fix in _isWordInScreenRange() method * add _nextGap(), _prevGap(), _gapInNextLine() and _gapInPrevLine() methods, so now key event handling code in cursor moving mode only focuses on drawing, calculation task is passed to these four methods. --- djvureader.lua | 20 +++-- unireader.lua | 203 ++++++++++++++++++++++++++++++------------------- 2 files changed, 138 insertions(+), 85 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index dd1892103..ed90352cf 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -53,15 +53,23 @@ function DJVUReader:_isEntireWordInScreenHeightRange(w) -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) return (w ~= nil) and - (self.cur_full_height - (w.y0 * self.globalzoom) >= - -self.offset_y) and - (self.cur_full_height - (w.y1 * self.globalzoom) <= - -self.offset_y + G_height) and - (w.x1 * self.globalzoom >= -self.offset_x) and - (w.x0 * self.globalzoom <= -self.offset_x + G_width) + (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) + and + (w.x1 * self.globalzoom >= -self.offset_x + or w.x0 * self.globalzoom <= -self.offset_x + G_width) end diff --git a/unireader.lua b/unireader.lua index d06f2b1d9..c5d0c20d1 100644 --- a/unireader.lua +++ b/unireader.lua @@ -148,7 +148,15 @@ function UniReader:getRectInScreen(x0, y0, x1, y1) w, h end --- make sure the whole word can be seen in screen +-- make sure the whole word/line can be seen in screen +-- @TODO when not in FIT_TO_CONTENT_WIDTH mode, +-- self.offset_{x,y} might be negative. 12.04 2012 (houqp) +function UniReader:_isEntireLineInScreenHeightRange(l) + return (l ~= nil) and + (l.y0 * self.globalzoom) >= -self.offset_y + and (l.y1 * self.globalzoom) <= -self.offset_y + G_height +end + function UniReader:_isEntireWordInScreenRange(w) return self:_isEntireWordInScreenHeightRange(w) and self:_isEntireWordInScreenWidthRange(w) @@ -156,8 +164,8 @@ end function UniReader:_isEntireWordInScreenHeightRange(w) return (w ~= nil) and - (w.y1 * self.globalzoom) >= -self.offset_y - and (w.y0 * self.globalzoom) <= -self.offset_y + G_height + (w.y0 * self.globalzoom) >= -self.offset_y + and (w.y1 * self.globalzoom) <= -self.offset_y + G_height end function UniReader:_isEntireWordInScreenWidthRange(w) @@ -169,10 +177,11 @@ end -- make sure at least part of the word can be seen in screen function UniReader:_isWordInScreenRange(w) return (w ~= nil) and - (w.y0 * self.globalzoom) >= -self.offset_y - and w.y1 * self.globalzoom <= -self.offset_y + G_height - and w.x1 * self.globalzoom >= -self.offset_x - and w.x0 * self.globalzoom <= -self.offset_x + G_width + ((w.y1 * self.globalzoom) >= -self.offset_y + or w.y0 * self.globalzoom <= -self.offset_y + G_height) + and + (w.x1 * self.globalzoom >= -self.offset_x + or w.x0 * self.globalzoom <= -self.offset_x + G_width) end function UniReader:toggleTextHighLight(word_list) @@ -268,7 +277,7 @@ function UniReader:drawCursorAfterWord(t, l, w) -- get height of line t[l][w] is in local _, _, _, h = self:zoomedRectCoordTransform(0, t[l].y0, 0, t[l].y1) -- get rect of t[l][w] - local x, y, wd, h = self:getRectInScreen(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) + local x, y, wd, _ = self:getRectInScreen(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) self.cursor:setHeight(h) self.cursor:moveTo(x+wd, y) self.cursor:draw() @@ -278,7 +287,7 @@ function UniReader:drawCursorBeforeWord(t, l, w) -- get height of line t[l][w] is in local _, _, _, h = self:zoomedRectCoordTransform(0, t[l].y0, 0, t[l].y1) -- get rect of t[l][w] - local x, y, _, h = self:getRectInScreen(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) + local x, y, _, _ = self:getRectInScreen(t[l][w].x0, t[l][w].y0, t[l][w].x1, t[l][w].y1) self.cursor:setHeight(h) self.cursor:moveTo(x, y) self.cursor:draw() @@ -305,6 +314,13 @@ function UniReader:startHighLightMode() return nil end + local function _isMovingForward(l, w) + return l.cur > l.start or (l.cur == l.start and w.cur > w.start) + end + + --------------------------------------- + -- some word handling help functions + --------------------------------------- local function _prevWord(t, cur_l, cur_w) if cur_l == 1 then if cur_w == 1 then @@ -361,10 +377,88 @@ function UniReader:startHighLightMode() end end - local function _isMovingForward(l, w) - return l.cur > l.start or (l.cur == l.start and w.cur > w.start) + --------------------------------------- + -- some gap handling help functions + --------------------------------------- + local function _nextGap(t, cur_l, cur_w) + local is_meet_end = false + + -- handle left end of line as special case. + if cur_w == 0 then + if cur_l == #t and #t[cur_l] == 1 then + is_meet_end = true + end + return cur_l, 1, is_meet_end + end + + cur_l, cur_w = _nextWord(t, cur_l, cur_w) + if cur_w == 1 then + cur_w = 0 + end + if cur_w ~= 0 and cur_l == #t and cur_w == #t[cur_l] then + is_meet_end = true + end + return cur_l, cur_w, is_meet_end end + local function _prevGap(t, cur_l, cur_w) + local is_meet_start = false + + -- handle left end of line as special case. + if cur_l == 1 and (cur_w == 1 or cur_w == 0) then -- in the first line + is_meet_start = true + return cur_l, 0, is_meet_start + end + if cur_w == 1 then -- not in the first line + return cur_l, 0, is_meet_start + elseif cur_w == 0 then + -- set to 1 so _prevWord() can find previous word in previous line + cur_w = 1 + end + + cur_l, cur_w = _prevWord(t, cur_l, cur_w) + return cur_l, cur_w, is_meet_end + end + + local function _gapInNextLine(t, cur_l, cur_w) + local is_meet_end = false + + if cur_l == #t then + -- already in last line + cur_w = #t[cur_l] + is_meet_end = true + else + -- handle left end of line as special case. + if cur_w == 0 then + cur_l = math.min(cur_l + 1, #t) + else + cur_l, cur_w = _wordInNextLine(t, cur_l, cur_w) + end + end + + return cur_l, cur_w, is_meet_end + end + + local function _gapInPrevLine(t, cur_l, cur_w) + local is_meet_start = false + + if cur_l == 1 then + -- already in first line + is_meet_start = true + cur_w = 0 + else + if cur_w == 0 then + -- goto left end of previous line + cur_l = math.max(cur_l - 1, 1) + else + cur_l, cur_w = _wordInPrevLine(t, cur_l, cur_w) + end + end + + return cur_l, cur_w, is_meet_start + end + + local l = {} local w = {} @@ -402,28 +496,11 @@ function UniReader:startHighLightMode() if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then if ev.code == KEY_FW_LEFT and not is_meet_start then is_meet_end = false - if w.cur == 1 then - -- goto left end of current line. _prevWord does not - -- understand and will not return zero w.cur - -- because zero does not point to a word, so we need to - -- handle left end manually here. - w.cur = 0 - w.new = 0 - if l.cur == 1 then - -- on left end of first line - is_meet_start = true - end - else - -- handle left end manually. - if w.cur == 0 then - w.cur = 1 - end - l.new, w.new = _prevWord(t, l.cur, w.cur) - end + l.new, w.new, is_meet_start = _prevGap(t, l.cur, w.cur) self.cursor:clear() if w.new ~= 0 - and not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) + and not self:_isEntireLineInScreenHeightRange(t[l.new]) and self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then -- word is in previous view local pageno = self:prevView() @@ -431,10 +508,10 @@ function UniReader:startHighLightMode() end -- update cursor - if w.cur == 0 then + if w.new == 0 then -- meet line left end, must be handled as special case - if self:_isEntireWordInScreenRange(t[l.cur][1]) then - self:drawCursorBeforeWord(t, l.cur, 1) + if self:_isEntireWordInScreenRange(t[l.new][1]) then + self:drawCursorBeforeWord(t, l.new, 1) end else if self:_isEntireWordInScreenRange(t[l.new][w.new]) then @@ -442,37 +519,23 @@ function UniReader:startHighLightMode() end end elseif ev.code == KEY_FW_RIGHT and not is_meet_end then - print(dump(l),dump(w)) is_meet_start = false - if w.cur == 0 then - w.cur = 1 - w.new = 1 - else - l.new, w.new = _nextWord(t, l.cur, w.cur) - if w.new == 1 then - -- Must be come from the right end of previous line, - -- so goto the left end of current line. - w.cur = 0 - w.new = 0 - end - end - if l.new == #t and w.new == #t[l.new] then - is_meet_end = true - end + l.new, w.new, is_meet_end = _nextGap(t, l.cur, w.cur) self.cursor:clear() - + -- we want to check whether the word is in screen range, + -- so trun gap into word local tmp_w = w.new - if w.cur == 0 then + if tmp_w == 0 then tmp_w = 1 end - if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + if not self:_isEntireLineInScreenHeightRange(t[l.new]) and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then local pageno = self:nextView() self:goto(pageno) end - if w.cur == 0 then + if w.new == 0 then -- meet line left end, must be handled as special case if self:_isEntireWordInScreenRange(t[l.new][1]) then self:drawCursorBeforeWord(t, l.new, 1) @@ -484,24 +547,15 @@ function UniReader:startHighLightMode() end elseif ev.code == KEY_FW_UP and not is_meet_start then is_meet_end = false - if w.cur == 0 then - -- goto left end of last line - l.new = math.max(l.cur - 1, 1) - elseif l.cur == 1 and w.cur == 1 then - -- already first word, to the left end of first line - w.new = 0 - is_meet_start = true - else - l.new, w.new = _wordInPrevLine(t, l.cur, w.cur) - end + l.new, w.new, is_meet_start = _gapInPrevLine(t, l.cur, w.cur) self.cursor:clear() local tmp_w = w.new - if w.cur == 0 then + if tmp_w == 0 then tmp_w = 1 end - if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + if not self:_isEntireLineInScreenHeightRange(t[l.new]) and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then -- goto next view of current page local pageno = self:prevView() @@ -519,16 +573,7 @@ function UniReader:startHighLightMode() end elseif ev.code == KEY_FW_DOWN and not is_meet_end then is_meet_start = false - if w.cur == 0 then - -- on the left end of current line, - -- goto left end of next line - l.new = math.min(l.cur + 1, #t) - else - l.new, w.new = _wordInNextLine(t, l.cur, w.cur) - end - if l.new == #t and w.new == #t[l.new] then - is_meet_end = true - end + l.new, w.new, is_meet_end = _gapInNextLine(t, l.cur, w.cur) self.cursor:clear() @@ -536,7 +581,7 @@ function UniReader:startHighLightMode() if w.cur == 0 then tmp_w = 1 end - if not self:_isEntireWordInScreenHeightRange(t[l.new][tmp_w]) + if not self:_isEntireLineInScreenHeightRange(t[l.new]) and self:_isEntireWordInScreenWidthRange(t[l.new][tmp_w]) then -- goto next view of current page local pageno = self:nextView() @@ -589,7 +634,7 @@ function UniReader:startHighLightMode() if w.cur == 0 then if l.cur == 1 then -- already at the begin of first line, nothing to toggle - return + return l, w, true else w.cur = 1 end @@ -603,7 +648,7 @@ function UniReader:startHighLightMode() end if w.new ~= 0 and - not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then + not self:_isEntireLineInScreenHeightRange(t[l.new][w.new]) then -- word out of left and right sides of current view should -- not trigger pan by page if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then @@ -640,7 +685,7 @@ function UniReader:startHighLightMode() is_meet_end = true end - if not self:_isEntireWordInScreenHeightRange(t[l.new][w.new]) then + if not self:_isEntireLineInScreenHeightRange(t[l.new]) then if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then local pageno = self:nextView() self:goto(pageno) From dcd485c84c4af46f2c5d5f41200b9f4b0c2c15ed Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 12 Apr 2012 17:31:01 +0800 Subject: [PATCH 151/183] fix: bug in highlighting words Properly highlight words that partially fit into screen range. --- djvureader.lua | 22 +++++++++++++++------ unireader.lua | 52 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index ed90352cf..220b3aeaf 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -64,12 +64,22 @@ end -- y axel in djvulibre starts from bottom function DJVUReader:_isWordInScreenRange(w) - return (w ~= nil) and - (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) - and - (w.x1 * self.globalzoom >= -self.offset_x - or w.x0 * self.globalzoom <= -self.offset_x + G_width) + 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 diff --git a/unireader.lua b/unireader.lua index c5d0c20d1..75f118a1b 100644 --- a/unireader.lua +++ b/unireader.lua @@ -137,15 +137,24 @@ end -- -- NOTE: this method does not check whether given area -- is can be shown in current screen. Make sure to check --- with _isEntireWordInScreenRange() before you want to --- draw on the returned area. +-- with _isEntireWordInScreenRange() or _isWordInScreenRange() +-- before you want to draw on the returned area. ---------------------------------------------------- function UniReader:getRectInScreen(x0, y0, x1, y1) x, y, w, h = self:zoomedRectCoordTransform(x0, y0, x1, y1) - return - x + self.offset_x, - y + self.offset_y, - w, h + x = x + self.offset_x + y = y + self.offset_y + if x < 0 then + w = w + x + x = 0 + end + if y < 0 then + h = h + y + y = 0 + end + if x + w > G_width then w = G_width - x end + if y + h > G_height then h = G_height - y end + return x, y, w, h end -- make sure the whole word/line can be seen in screen @@ -176,19 +185,27 @@ end -- make sure at least part of the word can be seen in screen function UniReader:_isWordInScreenRange(w) - return (w ~= nil) and - ((w.y1 * self.globalzoom) >= -self.offset_y - or w.y0 * self.globalzoom <= -self.offset_y + G_height) - and - (w.x1 * self.globalzoom >= -self.offset_x - or w.x0 * self.globalzoom <= -self.offset_x + G_width) + if not w then + return false + end + + is_entire_word_out_of_screen_height = + (w.y1 * self.globalzoom <= -self.offset_y) + or (w.y0 * 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 function UniReader:toggleTextHighLight(word_list) for _,text_item in ipairs(word_list) do for _,line_item in ipairs(text_item) do -- make sure that line is in screen range - if self:_isEntireWordInScreenHeightRange(line_item) then + if self:_isWordInScreenRange(line_item) then local x, y, w, h = self:getRectInScreen( line_item.x0, line_item.y0, line_item.x1, line_item.y1) @@ -265,6 +282,7 @@ function UniReader:_toggleTextHighLight(t, l0, w0, l1, w1) end for _l, _w in self:_wordIterFromRange(t, l0, w0, l1, w1) do + print("------------",_l, _w, dump(t[_l][_w])) if self:_isWordInScreenRange(t[_l][_w]) then -- blitbuffer module will take care of the out of screen range part. self:_toggleWordHighLight(t, _l, _w) @@ -648,7 +666,7 @@ function UniReader:startHighLightMode() end if w.new ~= 0 and - not self:_isEntireLineInScreenHeightRange(t[l.new][w.new]) then + not self:_isEntireLineInScreenHeightRange(t[l.new]) then -- word out of left and right sides of current view should -- not trigger pan by page if self:_isEntireWordInScreenWidthRange(t[l.new][w.new]) then @@ -740,13 +758,13 @@ function UniReader:startHighLightMode() elseif ev.code == KEY_FW_DOWN then is_meet_start = false if not is_meet_end then + -- handle left end of first line as special case if w.cur == 0 then - -- handle left end of first line as special case - tmp_l = math.min(tmp_l + 1, #t) tmp_w = 1 else - tmp_l, tmp_w = _wordInNextLine(t, l.new, w.new) + tmp_w = w.cur end + tmp_l, tmp_w = _wordInNextLine(t, l.cur, tmp_w) while not (tmp_l == l.cur and tmp_w == w.cur) do l, w, is_meet_end = _toggleNextWordHighLight(t, l, w) end From 1407579ea8aabb06549c0bc7ee97025f0a7f2296 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 12 Apr 2012 17:49:24 +0800 Subject: [PATCH 152/183] mod: remove debug code --- unireader.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/unireader.lua b/unireader.lua index 75f118a1b..e375f8986 100644 --- a/unireader.lua +++ b/unireader.lua @@ -282,7 +282,6 @@ function UniReader:_toggleTextHighLight(t, l0, w0, l1, w1) end for _l, _w in self:_wordIterFromRange(t, l0, w0, l1, w1) do - print("------------",_l, _w, dump(t[_l][_w])) if self:_isWordInScreenRange(t[_l][_w]) then -- blitbuffer module will take care of the out of screen range part. self:_toggleWordHighLight(t, _l, _w) From b11c5f414a8be4d315c94b349b3f803bba3e03a7 Mon Sep 17 00:00:00 2001 From: HW Date: Thu, 12 Apr 2012 20:02:10 +0200 Subject: [PATCH 153/183] highlighting: fixed segfault when there's no text on a PDF page also, add a few more whitespace characters from the Unicode standard. --- pdf.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pdf.c b/pdf.c index 96363b42e..e2abcbbaf 100644 --- a/pdf.c +++ b/pdf.c @@ -347,9 +347,10 @@ static int getPageText(lua_State *L) { /* table that contains all the lines */ lua_newtable(L); - ptr = page_text; line = 1; - while(ptr) { + for(ptr = page_text; ptr != NULL; ptr = ptr->next) { + if(ptr->text == NULL) continue; + /* table for the words */ lua_newtable(L); word = 1; @@ -367,7 +368,14 @@ static int getPageText(lua_State *L) { ptr->text[i].c == '\n' || ptr->text[i].c == '\v' || ptr->text[i].c == '\f' || - ptr->text[i].c == '\r' ) { + 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; @@ -415,8 +423,6 @@ static int getPageText(lua_State *L) { lua_settable(L, -3); lua_rawseti(L, -2, line++); - - ptr = ptr->next; } fz_free_text_span(page->doc->context, page_text); From 57d769e0ae06cdfa8fe9fefa7fab69c25c42baab Mon Sep 17 00:00:00 2001 From: HW Date: Thu, 12 Apr 2012 21:00:44 +0200 Subject: [PATCH 154/183] added wrapper function for input.waitForEvent that retries on EINTR --- filechooser.lua | 2 +- filesearcher.lua | 2 +- helppage.lua | 2 +- inputbox.lua | 2 +- keys.lua | 19 +++++++++++++++++++ selectmenu.lua | 2 +- unireader.lua | 8 ++++---- 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/filechooser.lua b/filechooser.lua index e96939035..82f821fbc 100644 --- a/filechooser.lua +++ b/filechooser.lua @@ -164,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 diff --git a/filesearcher.lua b/filesearcher.lua index efc6e2650..c0426029c 100644 --- a/filesearcher.lua +++ b/filesearcher.lua @@ -289,7 +289,7 @@ function FileSearcher:choose(keywords) 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 keydef = Keydef:new(ev.code, getKeyModifier()) diff --git a/helppage.lua b/helppage.lua index 25b9b0a9d..25b475521 100644 --- a/helppage.lua +++ b/helppage.lua @@ -77,7 +77,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 diff --git a/inputbox.lua b/inputbox.lua index c26b9e20f..ed581899b 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -163,7 +163,7 @@ function InputBox:input(ypos, height, title, d_text) fb:refresh(1, 20, ypos, w, h) while true do - 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 keydef = Keydef:new(ev.code, getKeyModifier()) diff --git a/keys.lua b/keys.lua index 0c155282e..a389bb085 100644 --- a/keys.lua +++ b/keys.lua @@ -254,3 +254,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" then + -- EINTR, we got interrupted. Try and restart + retry = true + else + retry = false + end + else + return ev + end + end +end diff --git a/selectmenu.lua b/selectmenu.lua index e32297195..da6f609bd 100644 --- a/selectmenu.lua +++ b/selectmenu.lua @@ -318,7 +318,7 @@ function SelectMenu:choose(ypos, height) 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 keydef = Keydef:new(ev.code, getKeyModifier()) diff --git a/unireader.lua b/unireader.lua index e375f8986..f95ebe275 100644 --- a/unireader.lua +++ b/unireader.lua @@ -508,7 +508,7 @@ function UniReader:startHighLightMode() -- first use cursor to place start pos for highlight while running do - 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_LEFT and not is_meet_start then @@ -727,7 +727,7 @@ function UniReader:startHighLightMode() -- go into highlight mode running = true while running do - 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_LEFT then @@ -1602,7 +1602,7 @@ function UniReader:showMenu() fb:refresh(1) while 1 do - local ev = input.waitForEvent() + local ev = input.saceWaitForEvent() ev.code = adjustKeyEvents(ev) if ev.type == EV_KEY and ev.value == EVENT_VALUE_KEY_PRESS then if ev.code == KEY_BACK or ev.code == KEY_MENU then @@ -1627,7 +1627,7 @@ end function UniReader:inputLoop() local keep_running = true while 1 do - 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() From 75f244eb6079082eba4a326038b9fc6e24a5ef0a Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 13 Apr 2012 10:37:50 +0800 Subject: [PATCH 155/183] fix: typo in unireader --- unireader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unireader.lua b/unireader.lua index f95ebe275..635f49295 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1602,7 +1602,7 @@ function UniReader:showMenu() fb:refresh(1) while 1 do - local ev = input.saceWaitForEvent() + 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_BACK or ev.code == KEY_MENU then From e0327ed4e8c5de039f02986af29b5b7d4bfedf15 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 13 Apr 2012 12:46:33 +0800 Subject: [PATCH 156/183] fix: enable screen rotate in crereader.lua --- crereader.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crereader.lua b/crereader.lua index a2f67b0e7..4fecea74e 100644 --- a/crereader.lua +++ b/crereader.lua @@ -225,8 +225,8 @@ function CREReader:adjustCreReaderCommands() -- delete commands self.commands:delGroup("[joypad]") self.commands:del(KEY_G, nil, "G") - self.commands:del(KEY_J, nil, "J") - self.commands:del(KEY_K, nil, "K") + 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") From cafa86a5bd8cdb9c2af10a520225a48bd047bb76 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 13 Apr 2012 15:19:42 +0800 Subject: [PATCH 157/183] fix condition in saveWaitForEvent() add newline escape sequence. close #106 --- keys.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keys.lua b/keys.lua index a389bb085..b32a4c949 100644 --- a/keys.lua +++ b/keys.lua @@ -262,7 +262,7 @@ function input.saveWaitForEvent(timeout) 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" then + if ev == "Waiting for input failed: 4\n" then -- EINTR, we got interrupted. Try and restart retry = true else From 3828a6c4407611e7d0ffab9b202e7df9d2770272 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Fri, 13 Apr 2012 17:09:15 +0800 Subject: [PATCH 158/183] fix: clear show_overlap in two column mode on KEY_FW_{LEFT, RIGHT} --- unireader.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unireader.lua b/unireader.lua index 635f49295..6d5b218e1 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1925,6 +1925,7 @@ function UniReader:addAllCommands() unireader.offset_y = unireader.min_offset_y -- bottom unireader:goto(unireader.pageno - 1) else + unireader.show_overlap = 0 unireader.offset_y = unireader.min_offset_y end elseif unireader.offset_x > 0 then @@ -1939,6 +1940,7 @@ function UniReader:addAllCommands() unireader.offset_y = unireader.pan_y unireader:goto(unireader.pageno + 1) else + unireader.show_overlap = 0 unireader.offset_y = unireader.pan_y end elseif unireader.offset_x < unireader.min_offset_x then From 0a8ca942186fc258dd723cbd928bcb104a112253 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 16:49:27 +0200 Subject: [PATCH 159/183] make highlight working in all zoom modes #103 This change introduce object's dest_x and dest_y coordinates which are needed in zoomedRectCoordTransform to make highlight scale and move according to current pan position. --- djvureader.lua | 11 +++++++++-- unireader.lua | 37 ++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index 220b3aeaf..ec2e53f26 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -37,9 +37,16 @@ end -- coordinate. So y0 should be taken with special care. ---------------------------------------------------- function DJVUReader:zoomedRectCoordTransform(x0, y0, x1, y1) + local x = self.dest_x + local y = self.dest_y + if self.offset_x < 0 or self.offset_y < 0 then + x = x + self.offset_x + y = y + self.offset_y + end + print("# zoomedRectCoordTransform x="..x.." y="..y.." dest="..self.dest_x..","..self.dest_y.." offset="..self.offset_x..","..self.offset_y); return - x0 * self.globalzoom, - self.cur_full_height - (y1 * self.globalzoom), + x0 * self.globalzoom + x, + self.cur_full_height - (y1 * self.globalzoom) + y, (x1 - x0) * self.globalzoom, (y1 - y0) * self.globalzoom end diff --git a/unireader.lua b/unireader.lua index 6d5b218e1..275a30076 100644 --- a/unireader.lua +++ b/unireader.lua @@ -42,6 +42,8 @@ UniReader = { cur_full_height = 0, offset_x = 0, offset_y = 0, + dest_x = 0, -- real offset_x when it's smaller than screen, so it's centered + dest_y = 0, min_offset_x = 0, min_offset_y = 0, content_top = 0, -- for ZOOM_FIT_TO_CONTENT_WIDTH_PAN (prevView) @@ -122,9 +124,16 @@ end -- zoomed page size with width and height. ---------------------------------------------------- function UniReader:zoomedRectCoordTransform(x0, y0, x1, y1) - return - x0 * self.globalzoom, - y0 * self.globalzoom, + local x = self.dest_x + local y = self.dest_y + if self.offset_x < 0 or self.offset_y < 0 then + x = x + self.offset_x + y = y + self.offset_y + end + print("# zoomedRectCoordTransform x="..x.." y="..y.." dest="..self.dest_x..","..self.dest_y.." offset="..self.offset_x..","..self.offset_y); + return + x0 * self.globalzoom + x, + y0 * self.globalzoom + y, (x1 - x0) * self.globalzoom, (y1 - y0) * self.globalzoom end @@ -142,8 +151,6 @@ end ---------------------------------------------------- function UniReader:getRectInScreen(x0, y0, x1, y1) x, y, w, h = self:zoomedRectCoordTransform(x0, y0, x1, y1) - x = x + self.offset_x - y = y + self.offset_y if x < 0 then w = w + x x = 0 @@ -1229,37 +1236,37 @@ function UniReader:show(no) end self.pagehash = pagehash local bb = self.cache[pagehash].bb - local dest_x = 0 - local dest_y = 0 + self.dest_x = 0 + self.dest_y = 0 if bb:getWidth() - offset_x < width then -- we can't fill the whole output width, center the content - dest_x = (width - (bb:getWidth() - offset_x)) / 2 + self.dest_x = (width - (bb:getWidth() - offset_x)) / 2 end if bb:getHeight() - offset_y < height and self.globalzoommode ~= self.ZOOM_FIT_TO_CONTENT_WIDTH_PAN then -- we can't fill the whole output height and not in -- ZOOM_FIT_TO_CONTENT_WIDTH_PAN mode, center the content - dest_y = (height - (bb:getHeight() - offset_y)) / 2 + self.dest_y = (height - (bb:getHeight() - offset_y)) / 2 elseif self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_WIDTH_PAN and self.offset_y > 0 then -- if we are in ZOOM_FIT_TO_CONTENT_WIDTH_PAN mode and turning to -- the top of the page, we might leave an empty space between the -- page top and screen top. - dest_y = self.offset_y + self.dest_y = self.offset_y end - if dest_x or dest_y then + if self.dest_x or self.dest_y then fb.bb:paintRect(0, 0, width, height, 8) end - print("# blitFrom dest_off:("..dest_x..", "..dest_y.. + print("# blitFrom dest_off:("..self.dest_x..", "..self.dest_y.. "), src_off:("..offset_x..", "..offset_y.."), ".. "width:"..width..", height:"..height) - fb.bb:blitFrom(bb, dest_x, dest_y, offset_x, offset_y, width, height) + fb.bb:blitFrom(bb, self.dest_x, self.dest_y, offset_x, offset_y, width, height) print("## self.show_overlap "..self.show_overlap) if self.show_overlap < 0 then - fb.bb:dimRect(0,0, width, dest_y - self.show_overlap) + fb.bb:dimRect(0,0, width, self.dest_y - self.show_overlap) elseif self.show_overlap > 0 then - fb.bb:dimRect(0,dest_y + height - self.show_overlap, width, self.show_overlap) + fb.bb:dimRect(0,self.dest_y + height - self.show_overlap, width, self.show_overlap) end self.show_overlap = 0 From 4d49e9f28bead2db51558c159a09006b57299060 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 17:57:42 +0200 Subject: [PATCH 160/183] correct two-column margin calculation respecting globalzoom --- unireader.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/unireader.lua b/unireader.lua index 275a30076..d59d9e386 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1186,11 +1186,13 @@ function UniReader:setzoom(page, preCache) or self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH_MARGIN then local margin = self.pan_margin if self.globalzoommode == self.ZOOM_FIT_TO_CONTENT_HALF_WIDTH then margin = 0 end - self.globalzoom = width / (x1 - x0 + margin) + local pg_margin = 0 -- margin scaled to page size + if margin > 0 then pg_margin = margin * 2 / self.globalzoom end + self.globalzoom = width / (x1 - x0 + pg_margin) self.offset_x = -1 * x0 * self.globalzoom * 2 + margin - self.globalzoom = height / (y1 - y0) + self.globalzoom = height / (y1 - y0 + pg_margin) self.offset_y = -1 * y0 * self.globalzoom * 2 + margin - self.globalzoom = width / (x1 - x0 + margin) * 2 + self.globalzoom = width / (x1 - x0 + pg_margin) * 2 print("column mode offset:"..self.offset_x.."*"..self.offset_y.." zoom:"..self.globalzoom); self.globalzoommode = self.ZOOM_BY_VALUE -- enable pan mode self.pan_x = self.offset_x From 96958c62ca9c988f9e5abde4326d5d340e4216ed Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 18:10:11 +0200 Subject: [PATCH 161/183] added UniReader:screenOffset and use it --- djvureader.lua | 8 +------- unireader.lua | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/djvureader.lua b/djvureader.lua index ec2e53f26..f9142c366 100644 --- a/djvureader.lua +++ b/djvureader.lua @@ -37,13 +37,7 @@ end -- coordinate. So y0 should be taken with special care. ---------------------------------------------------- function DJVUReader:zoomedRectCoordTransform(x0, y0, x1, y1) - local x = self.dest_x - local y = self.dest_y - if self.offset_x < 0 or self.offset_y < 0 then - x = x + self.offset_x - y = y + self.offset_y - end - print("# zoomedRectCoordTransform x="..x.." y="..y.." dest="..self.dest_x..","..self.dest_y.." offset="..self.offset_x..","..self.offset_y); + local x,y = self:screenOffset() return x0 * self.globalzoom + x, self.cur_full_height - (y1 * self.globalzoom) + y, diff --git a/unireader.lua b/unireader.lua index d59d9e386..47125b968 100644 --- a/unireader.lua +++ b/unireader.lua @@ -118,19 +118,26 @@ end -- highlight support ---------------------------------------------------- +function UniReader:screenOffset() + local x = self.dest_x + local y = self.dest_y + if self.offset_x < 0 then + x = x + self.offset_x + end + if self.offset_y < 0 then + y = y + self.offset_y + end + print("# screenOffset "..x..","..y) + return x,y +end + ---------------------------------------------------- -- Given coordinates of four corners in original page -- size and return coordinate of upper left conner in -- zoomed page size with width and height. ---------------------------------------------------- function UniReader:zoomedRectCoordTransform(x0, y0, x1, y1) - local x = self.dest_x - local y = self.dest_y - if self.offset_x < 0 or self.offset_y < 0 then - x = x + self.offset_x - y = y + self.offset_y - end - print("# zoomedRectCoordTransform x="..x.." y="..y.." dest="..self.dest_x..","..self.dest_y.." offset="..self.offset_x..","..self.offset_y); + local x,y = self:screenOffset() return x0 * self.globalzoom + x, y0 * self.globalzoom + y, From 81c6dc5997ace222dd5266af924da099247bf488 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 20:46:10 +0200 Subject: [PATCH 162/183] commands.map is very large, impacts startup performance on device --- unireader.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unireader.lua b/unireader.lua index 47125b968..86b2cfbbc 100644 --- a/unireader.lua +++ b/unireader.lua @@ -2021,5 +2021,6 @@ function UniReader:addAllCommands() unireader:goto(unireader.pageno) end ) - print("## defined commands "..dump(self.commands.map)) + -- commands.map is very large, impacts startup performance on device + --print("## defined commands "..dump(self.commands.map)) end From ffa663d37d98caeeb4a3d63b75465854dd38f002 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 20:52:30 +0200 Subject: [PATCH 163/183] added few more keys to inputbox --- inputbox.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inputbox.lua b/inputbox.lua index ed581899b..1ceea8a0a 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -208,6 +208,11 @@ function InputBox:addAllCommands() {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, "", From a65090025c1184ba874cc2664445d81cfb44253a Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 23:53:36 +0200 Subject: [PATCH 164/183] display crash.log on error --- launchpad/kpdf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launchpad/kpdf.sh b/launchpad/kpdf.sh index a2b714b75..5c0611f2e 100755 --- a/launchpad/kpdf.sh +++ b/launchpad/kpdf.sh @@ -8,7 +8,7 @@ cd /mnt/us/kindlepdfviewer/ 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 +./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 From 56ddad6a38872395a7eaa4a19b0d0f1c0e9a54d1 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 23:54:06 +0200 Subject: [PATCH 165/183] remove page:getPageText debug dump to improve performance on device --- pdfreader.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdfreader.lua b/pdfreader.lua index c6d55d9ad..4c2772104 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -41,7 +41,7 @@ function PDFReader:getText(pageno) return nil end local text = page:getPageText() - print(dump(text)) + --print("## page:getPageText "..dump(text)) -- performance impact on device page:close() return text end From b6d167f8ca3d068e2e90fb95a9acc3ad88348338 Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Fri, 13 Apr 2012 23:54:06 +0200 Subject: [PATCH 166/183] remove page:getPageText debug dump to improve performance on device And leave it in unireader if we can't find text on page --- pdfreader.lua | 2 +- unireader.lua | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pdfreader.lua b/pdfreader.lua index c6d55d9ad..4c2772104 100644 --- a/pdfreader.lua +++ b/pdfreader.lua @@ -41,7 +41,7 @@ function PDFReader:getText(pageno) return nil end local text = page:getPageText() - print(dump(text)) + --print("## page:getPageText "..dump(text)) -- performance impact on device page:close() return text end diff --git a/unireader.lua b/unireader.lua index 86b2cfbbc..3026bcb2a 100644 --- a/unireader.lua +++ b/unireader.lua @@ -342,6 +342,8 @@ function UniReader:startHighLightMode() end end + print("## _findFirstWordInView none found in "..dump(t)) + return nil end From e4841e88c4aec0243f97666219997441807f6ab0 Mon Sep 17 00:00:00 2001 From: HW Date: Sat, 14 Apr 2012 00:38:18 +0200 Subject: [PATCH 167/183] added line spacing setting for crereader this patch also introduces an interface for setFontSize and setStyleSheet. Hopefully, we can use the latter to send customized CSS to CREngine as Lua strings. This functionality is however untested for now. --- cre.cpp | 27 +++++++++++++++++++++++++++ crereader.lua | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/cre.cpp b/cre.cpp index 0f6ca672a..feea80114 100644 --- a/cre.cpp +++ b/cre.cpp @@ -325,6 +325,30 @@ static int zoomFont(lua_State *L) { 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"); @@ -422,6 +446,9 @@ static const struct luaL_Reg credocument_meth[] = { {"getToc", getTableOfContent}, /*--- set methods ---*/ {"setFontFace", setFontFace}, + {"setFontSize", setFontSize}, + {"setDefaultInterlineSpace", setDefaultInterlineSpace}, + {"setStyleSheet", setStyleSheet}, /* --- control methods ---*/ {"gotoPage", gotoPage}, {"gotoPercent", gotoPercent}, diff --git a/crereader.lua b/crereader.lua index 4fecea74e..dab931cec 100644 --- a/crereader.lua +++ b/crereader.lua @@ -9,6 +9,8 @@ CREReader = UniReader:new{ gamma_index = 15, font_face = nil, + + line_space_percent = 100, } function CREReader:init() @@ -39,6 +41,8 @@ function CREReader:open(filename) return false, self.doc -- will contain error message end + self.doc:setDefaultInterlineSpace(self.line_space_percent) + return true end @@ -53,6 +57,9 @@ function CREReader:loadSpecialSettings() 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() @@ -67,6 +74,7 @@ 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() @@ -245,20 +253,44 @@ function CREReader:adjustCreReaderCommands() self.commands:del(KEY_N, MOD_SHIFT, "N") -- show highlights -- overwrite commands - self.commands:add(KEY_PGFWD, MOD_SHIFT_OR_ALT, ">", + 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_OR_ALT, "<", + 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)) From 30228890885a34ca470b35238a27598bd9dad914 Mon Sep 17 00:00:00 2001 From: HW Date: Sat, 14 Apr 2012 01:09:38 +0200 Subject: [PATCH 168/183] kill our own child process. not quite finished. we can now kill and wait for the slider watcher subprocess that we spawned. However, it will have a subprocess itself, the lipc-wait-event process. That one currently stays alive until it finally sends something. --- input.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/input.c b/input.c index 60b58afb3..17f22c7b7 100644 --- a/input.c +++ b/input.c @@ -22,6 +22,9 @@ #include #include #include "input.h" +#include +#include +#include #define OUTPUT_SIZE 21 #define CODE_IN_SAVER 10000 @@ -29,6 +32,7 @@ #define NUM_FDS 4 int inputfds[4] = { -1, -1, -1, -1 }; +int slider_pid = -1; int findFreeFdSlot() { int i; @@ -98,6 +102,7 @@ static int openInputDevice(lua_State *L) { } else { close(pipefd[1]); inputfds[fd] = pipefd[0]; + slider_pid = childpid; } } else { inputfds[fd] = open(inputdevice, O_RDONLY | O_NONBLOCK, 0); @@ -124,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; } From f905158b6aacce92590698fc04fb578d740d6f66 Mon Sep 17 00:00:00 2001 From: HW Date: Sat, 14 Apr 2012 01:11:13 +0200 Subject: [PATCH 169/183] reverted removal of last-doc shortcut, introduced framework restart --- launchpad/kpdf.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launchpad/kpdf.ini b/launchpad/kpdf.ini index 870314044..4ad186020 100755 --- a/launchpad/kpdf.ini +++ b/launchpad/kpdf.ini @@ -1,2 +1,7 @@ [Actions] +# 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 From db1b2cd01a7805052f92c266d0f51ac8346ab85a Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 14 Apr 2012 12:20:04 +0800 Subject: [PATCH 170/183] fix: add back KEY_FW{LEFT,RIGHT} commands to NumInputBox --- inputbox.lua | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/inputbox.lua b/inputbox.lua index 1ceea8a0a..55c70c9ff 100644 --- a/inputbox.lua +++ b/inputbox.lua @@ -64,7 +64,7 @@ function InputBox:addChar(char) -- 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 = 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 @@ -88,7 +88,7 @@ function InputBox:delChar() self.cursor:clear() -- draw new text - self.input_string = self.input_string:sub(0,cur_index-1).. + 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 @@ -238,7 +238,7 @@ function InputBox:addAllCommands() 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, + fb:refresh(1, self.input_start_x-5, self.ypos, self.input_slot_w, self.h) end end @@ -303,6 +303,26 @@ function NumInputBox:addAllCommands() ) 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) From c71f5c6f6a2eb11098badcc27582fe32790f00e2 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 14 Apr 2012 13:41:44 +0800 Subject: [PATCH 171/183] add: sleep and usleep in util module Now using it in commands.lua --- commands.lua | 2 +- util.c | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/commands.lua b/commands.lua index b818f258b..1dd6d6efb 100644 --- a/commands.lua +++ b/commands.lua @@ -174,7 +174,7 @@ function Commands:new(obj) obj:add(KEY_OUTOF_SCREEN_SAVER, nil, "Slider", "toggle screen saver", function() - os.execute("sleep 3") + util.sleep(3) --os.execute("killall -stop cvm") fb:setOrientation(Screen.kpv_rotation_mode) Screen:resotreFromSavedBB() diff --git a/util.c b/util.c index e8978f350..061847723 100644 --- a/util.c +++ b/util.c @@ -17,6 +17,7 @@ */ #include +#include #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); @@ -57,6 +71,8 @@ static int isEmulated(lua_State *L) { static const struct luaL_Reg util_func[] = { {"gettime", gettime}, + {"sleep", util_sleep}, + {"usleep", util_usleep}, {"utf8charcode", utf8charcode}, {"isEmulated", isEmulated}, {NULL, NULL} From cde796f6b5159dcef4b7a3b1afe07037d40a2d5e Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sat, 14 Apr 2012 14:27:17 +0800 Subject: [PATCH 172/183] fix full screen refresh command, close #99 --- unireader.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unireader.lua b/unireader.lua index 3026bcb2a..095eea10f 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1862,8 +1862,13 @@ function UniReader:addAllCommands() self.commands:add(KEY_R, MOD_SHIFT, "R", "manual full screen refresh", function(unireader) - unireader.rcount = 1 + -- eink will not refresh if nothing is changeed on the screen + -- so we fake a change here. + fb.bb:invertRect(0, 0, 1, 1) + fb:refresh(1) + fb.bb:invertRect(0, 0, 1, 1) fb:refresh(0) + unireader.rcount = 1 end) self.commands:add(KEY_HOME,nil,"Home", "exit application", From eeae5982dd3ca7211f6c1b25e8eb26398ab0d5dc Mon Sep 17 00:00:00 2001 From: Dobrica Pavlinusic Date: Sat, 14 Apr 2012 16:23:41 +0200 Subject: [PATCH 173/183] added reading of pan_margin settings --- unireader.lua | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/unireader.lua b/unireader.lua index 095eea10f..b1b5228ba 100644 --- a/unireader.lua +++ b/unireader.lua @@ -874,6 +874,11 @@ end --[ following are default methods ]-- function UniReader:initGlobalSettings(settings) + local pan_margin = settings:readSetting("pan_margin") + if pan_margin then + self.pan_margin = pan_margin + end + local pan_overlap_vertical = settings:readSetting("pan_overlap_vertical") if pan_overlap_vertical then self.pan_overlap_vertical = pan_overlap_vertical From e52b4808b17d4259d28263216a1168d8c1cac60b Mon Sep 17 00:00:00 2001 From: HW Date: Sat, 14 Apr 2012 19:28:59 +0200 Subject: [PATCH 174/183] fix typo --- commands.lua | 2 +- screen.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/commands.lua b/commands.lua index 1dd6d6efb..b13dcf39a 100644 --- a/commands.lua +++ b/commands.lua @@ -177,7 +177,7 @@ function Commands:new(obj) util.sleep(3) --os.execute("killall -stop cvm") fb:setOrientation(Screen.kpv_rotation_mode) - Screen:resotreFromSavedBB() + Screen:restoreFromSavedBB() fb:refresh(0) end ) diff --git a/screen.lua b/screen.lua index 25edb1096..f7c9b3f6c 100644 --- a/screen.lua +++ b/screen.lua @@ -89,7 +89,7 @@ function Screen:saveCurrentBB() self.saved_bb:blitFullFrom(fb.bb) end -function Screen:resotreFromSavedBB() +function Screen:restoreFromSavedBB() self:restoreFromBB(self.saved_bb) end From 38afb59b3f5cf4e28c2afc95c13be5fd5222a28c Mon Sep 17 00:00:00 2001 From: HW Date: Sat, 14 Apr 2012 22:32:05 +0200 Subject: [PATCH 175/183] added interface to get blitbuffers from JPEG/PNG files this is supposed to help us in the UI code. --- Makefile | 5 +- image.lua | 25 +++++++++ kpdfview.c | 2 + mupdfimg.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++ mupdfimg.h | 26 +++++++++ 5 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 image.lua create mode 100644 mupdfimg.c create mode 100644 mupdfimg.h diff --git a/Makefile b/Makefile index 7ae242008..21983c84f 100644 --- a/Makefile +++ b/Makefile @@ -87,7 +87,7 @@ LUALIB := $(LUADIR)/src/liblua.a all:kpdfview -kpdfview: kpdfview.o einkfb.o pdf.o blitbuffer.o drawcontext.o input.o util.o ft.o lfs.o $(MUPDFLIBS) $(THIRDPARTYLIBS) $(LUALIB) djvu.o $(DJVULIBS) cre.o $(CRENGINELIBS) +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 \ @@ -98,6 +98,7 @@ 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) \ @@ -114,7 +115,7 @@ slider_watcher: slider_watcher.c ft.o: %.o: %.c $(CC) -c $(KPDFREADER_CFLAGS) -I$(FREETYPEDIR)/include $< -o $@ -kpdfview.o pdf.o blitbuffer.o util.o drawcontext.o einkfb.o input.o: %.o: %.c +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 diff --git a/image.lua b/image.lua new file mode 100644 index 000000000..be1e0210d --- /dev/null +++ b/image.lua @@ -0,0 +1,25 @@ +Image = {} + +function Image._getFileData(filename) + local f = io.open("test.png") + 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 + diff --git a/kpdfview.c b/kpdfview.c index a09a9a0ab..d0003ae7f 100644 --- a/kpdfview.c +++ b/kpdfview.c @@ -26,6 +26,7 @@ #include "blitbuffer.h" #include "drawcontext.h" #include "pdf.h" +#include "mupdfimg.h" #include "djvu.h" #include "cre.h" #include "einkfb.h" @@ -59,6 +60,7 @@ int main(int argc, char **argv) { luaopen_input(L); luaopen_util(L); luaopen_ft(L); + luaopen_mupdfimg(L); luaopen_lfs(L); diff --git a/mupdfimg.c b/mupdfimg.c new file mode 100644 index 000000000..cdabaafca --- /dev/null +++ b/mupdfimg.c @@ -0,0 +1,154 @@ +/* + KindlePDFViewer: MuPDF abstraction for Lua, only image part + Copyright (C) 2012 Hans-Werner Hilse + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include + +#include "blitbuffer.h" +#include "mupdfimg.h" +#include +#include + + +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; +} diff --git a/mupdfimg.h b/mupdfimg.h new file mode 100644 index 000000000..48b6d4f94 --- /dev/null +++ b/mupdfimg.h @@ -0,0 +1,26 @@ +/* + KindlePDFViewer: MuPDF abstraction for Lua, only image part + Copyright (C) 2012 Hans-Werner Hilse + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef _MUPDFIMG_H +#define _MUPDFIMG_H + +#include +#include +#include + +int luaopen_mupdfimg(lua_State *L); +#endif From b6d75b84ecb315b9df15312e0f876bc0d571c56d Mon Sep 17 00:00:00 2001 From: HW Date: Sun, 15 Apr 2012 01:59:49 +0200 Subject: [PATCH 176/183] Added widget abstraction framework An example for using it: --snip require "widget" require "font" fb = einkfb.open("/dev/fb0") G_width, G_height = fb:getSize() dialog = CenterContainer:new({ dimen = { w = G_width, h = G_height }, VerticalGroup:new({ align = "center", FrameContainer:new({ CenterContainer:new({ dimen = { w = 400, h = 200 }, TextWidget:new({ text = "Hi there! jgVJV", face = Font:getFace("cfont", 30) }) }) }), ImageWidget:new({ file = "test.png" }), FrameContainer:new({ CenterContainer:new({ dimen = { w = 300, h = 200 }, TextWidget:new({ text = "another box", face = Font:getFace("cfont", 30) }) }) }) }) }) dialog:paintTo(fb.bb, 0, 0) fb:refresh(0) input.waitForEvent() --snip --- font.lua | 2 +- widget.lua | 263 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 widget.lua diff --git a/font.lua b/font.lua index 5c0f3a584..ffc0bd1d1 100644 --- a/font.lua +++ b/font.lua @@ -55,7 +55,7 @@ function Font:getFace(font, size) end self.faces[font..size] = face end - return { ftface = face, hash = font..size } + return { size = size, ftface = face, hash = font..size } end function Font:_readList(target, dir, effective_dir) diff --git a/widget.lua b/widget.lua new file mode 100644 index 000000000..aa8ba1034 --- /dev/null +++ b/widget.lua @@ -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 From 4cd63b71df033bc68e642a0e070a4281f1758f51 Mon Sep 17 00:00:00 2001 From: HW Date: Sun, 15 Apr 2012 02:26:24 +0200 Subject: [PATCH 177/183] bugfix, removed old test constant --- image.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/image.lua b/image.lua index be1e0210d..14b1d7ead 100644 --- a/image.lua +++ b/image.lua @@ -1,7 +1,7 @@ Image = {} function Image._getFileData(filename) - local f = io.open("test.png") + local f = io.open(filename) local data = f:read("*a") f:close() return data From 4714057f5ba71a995c2408201b3ebdc7e3807ad0 Mon Sep 17 00:00:00 2001 From: HW Date: Sun, 15 Apr 2012 02:28:48 +0200 Subject: [PATCH 178/183] added infomessage dialog implementation --- dialog.lua | 30 ++++++++++++++++++++++++++++++ font.lua | 3 +++ 2 files changed, 33 insertions(+) create mode 100644 dialog.lua diff --git a/dialog.lua b/dialog.lua new file mode 100644 index 000000000..129e36813 --- /dev/null +++ b/dialog.lua @@ -0,0 +1,30 @@ +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, + 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 diff --git a/font.lua b/font.lua index ffc0bd1d1..cdf658956 100644 --- a/font.lua +++ b/font.lua @@ -25,6 +25,9 @@ Font = { -- 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", From 676e4268ff83bebacb4366fede3380ef1632a42f Mon Sep 17 00:00:00 2001 From: HW Date: Sun, 15 Apr 2012 02:34:09 +0200 Subject: [PATCH 179/183] display document open error message --- dialog.lua | 1 + reader.lua | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dialog.lua b/dialog.lua index 129e36813..e35a61783 100644 --- a/dialog.lua +++ b/dialog.lua @@ -10,6 +10,7 @@ function InfoMessage:show(text) dimen = { w = G_width, h = G_height }, FrameContainer:new({ margin = 2, + background = 0, HorizontalGroup:new({ align = "center", ImageWidget:new({ diff --git a/reader.lua b/reader.lua index f28596b27..e42f25957 100755 --- a/reader.lua +++ b/reader.lua @@ -26,6 +26,7 @@ require "settings" require "screen" require "keys" require "commands" +require "dialog" -- option parsing: longopts = { @@ -55,7 +56,9 @@ function openFile(filename) 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 From 31c33a87c9717ac1d686e65b76bbaedbe483e3fd Mon Sep 17 00:00:00 2001 From: HW Date: Sun, 15 Apr 2012 02:34:49 +0200 Subject: [PATCH 180/183] add resource: info icon --- resources/info-i.png | Bin 0 -> 1107 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/info-i.png diff --git a/resources/info-i.png b/resources/info-i.png new file mode 100644 index 0000000000000000000000000000000000000000..bf68406f42bc48324af3357b1ece2d1357c78556 GIT binary patch literal 1107 zcmV-Z1g!gsP)Px(4M{{nRA}DSn!iiyP!z{+)7rGAl%h+G)(B2H4sASxXk3SEMeo4Pny zH}elTWN|3crJJ}o2to%3NgT9v@dpUfMXbgCAlDM|hUPt=k~ZnRX=u(|zV}0V&bjBu zb*ZWfoMs+;iLbA(VzDTSVyRSme0;1_D%EPW*=!O7;rIK;#>Qxxjz*)gSS%ioFE1~9 zyyIGbM1ff=|As>wbElCo`aecAs(sLXqNz%Zf%jI%9 zown9N)9G}%Tpkp3Hk%z>Y-O`qi=ib+N+c44&IyS`!lbttL+5fiO!w`}(Q~;RE;P^c z_NQT<=h2}D>cluM4u&W82?)e|kICX_A%e|qS%_dHLq7N-e5>AFHl}f##udlCh^#6~@ zZ)lF=a71@@c6vi^Y;54@a~$^@x>l><*T;oIK^M2(Ztw2y;+G}=!bXD@1i_MPY;$w- z;^N}%?XBHz*X#BB`}^bLvd>Xm` z^LRY1R?8$bgwT>cQuFCbrGh3gF)`uy`%V7Xg2B>XR004{tya+_LZOh&2}DCxRh1wJ zG>JeUu)e;2baeFc@?xs>$2Ni>G@nL>{%1a)@AC4}R%nW%@a2}4mTZ8gC`yBlL?ZZd zfk40p=tv}@We=LB@#Pwgh7Ez(K_vhH(P-2uJAe)<0RV`_VorhXpb`Lpcs%YDXbsiE z(sa;)_@gxSdcDbHa`-=UGMVhu0@}5*wzf9>(BF_&HMWCx51qyqe}q7iq#-m(l2(oo z@P{=tO&dZ_O-)%mtifzyR##UIp^*pGUB_ZL1ISP)WM(mScXu~FK5p><5@FQ4wY7D6 zdiwtUZnEk6`1r`@^L@>#4Mx5F4%J;Q*XQRa!k!pH=<%3(OJHiK-q-rVOz?HH>FH@R zY0m8W!u+CWetsSqnqe42{cejQ{r@PF$y6$p{r!EzF00XK$g&I}gb*r!P4}C1j3h~t zBz-=gVb11xK9kAxeT_M9`RUX(vr`xPPJKGz)JG}~eh%c+hgU=L#Lb1@Z`%Ku&;n@t Z?GJHHNn?*EVx#~7002ovPDHLkV1kK#BUk_c literal 0 HcmV?d00001 From 552246ba13eb3ad08cbe50b174e54a75b2717f33 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 15 Apr 2012 11:01:14 +0800 Subject: [PATCH 181/183] copy resources dir on customupdate --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 21983c84f..436c9446b 100644 --- a/Makefile +++ b/Makefile @@ -215,6 +215,7 @@ customupdate: all 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) From f24f2a01f635509da308b41474d173334e74bad3 Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 15 Apr 2012 11:02:42 +0800 Subject: [PATCH 182/183] use InfoMessage for empty TOC, jump history and highlights --- dialog.lua | 12 +++++++++ unireader.lua | 71 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 55 insertions(+), 28 deletions(-) diff --git a/dialog.lua b/dialog.lua index e35a61783..7ae25e108 100644 --- a/dialog.lua +++ b/dialog.lua @@ -29,3 +29,15 @@ function InfoMessage:show(text) 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 diff --git a/unireader.lua b/unireader.lua index b1b5228ba..255360736 100644 --- a/unireader.lua +++ b/unireader.lua @@ -1532,17 +1532,21 @@ function UniReader:showToc() (" "):rep(v.depth-1)..self:cleanUpTocTitle(v.title)) end - toc_menu = SelectMenu:new{ - menu_title = "Table of Contents", - item_array = menu_items, - no_item_msg = "This document does not have a Table of Contents.", - } - item_no = toc_menu:choose(0, fb.bb:getHeight()) - - if item_no then - self:gotoTocEntry(self.toc[item_no]) + if #menu_items == 0 then + showInfoMsgWithDelay( + "This document does not have a TOC.", 2000, 1) else - self:redrawCurrentPage() + toc_menu = SelectMenu:new{ + menu_title = "Table of Contents", + item_array = menu_items, + } + item_no = toc_menu:choose(0, fb.bb:getHeight()) + + if item_no then + self:gotoTocEntry(self.toc[item_no]) + else + self:redrawCurrentPage() + end end end @@ -1552,17 +1556,22 @@ function UniReader:showJumpStack() table.insert(menu_items, v.datetime.." -> Page "..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) + + if #menu_items == 0 then + showInfoMsgWithDelay( + "No jump history found.", 2000, 1) else - self:redrawCurrentPage() + jump_menu = SelectMenu:new{ + menu_title = "Jump Keeper (current page: "..self.pageno..")", + item_array = menu_items, + } + item_no = jump_menu:choose(0, fb.bb:getHeight()) + if item_no then + local jump_item = self.jump_stack[item_no] + self:goto(jump_item.page) + else + self:redrawCurrentPage() + end end end @@ -1578,14 +1587,20 @@ function UniReader:showHighLight() end end end - toc_menu = SelectMenu:new{ - menu_title = "HighLights", - item_array = menu_items, - no_item_msg = "No HighLight found.", - } - item_no = toc_menu:choose(0, fb.bb:getHeight()) - if item_no then - self:goto(highlight_dict[item_no].page) + if #menu_items == 0 then + showInfoMsgWithDelay( + "No HighLights found.", 2000, 1) + else + toc_menu = SelectMenu:new{ + menu_title = "HighLights", + item_array = menu_items, + } + item_no = toc_menu:choose(0, fb.bb:getHeight()) + if item_no then + self:goto(highlight_dict[item_no].page) + else + self:redrawCurrentPage() + end end end From 5e867d38ef66d94e8b22b55f5cdf3339b8ffd38e Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Sun, 15 Apr 2012 14:20:26 +0800 Subject: [PATCH 183/183] show infomessage on document open --- reader.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reader.lua b/reader.lua index e42f25957..f1aed3f34 100755 --- a/reader.lua +++ b/reader.lua @@ -48,6 +48,8 @@ function openFile(filename) 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)