From a231b944c13779c0fdbde3759f1b081c0154cafc Mon Sep 17 00:00:00 2001 From: Qingping Hou Date: Thu, 15 Mar 2012 17:03:09 +0800 Subject: [PATCH 01/32] 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 02/32] 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 03/32] 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 04/32] 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 05/32] 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 06/32] 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 07/32] 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 08/32] 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 09/32] 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 10/32] 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 11/32] 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 12/32] 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 13/32] 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 14/32] 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 15/32] 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 16/32] 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 17/32] 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 18/32] 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 19/32] 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 20/32] 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 21/32] 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 22/32] 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 23/32] 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 24/32] 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 25/32] 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 26/32] 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 27/32] 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 28/32] 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 29/32] 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 30/32] 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 31/32] 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 32/32] 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 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 Z_;0Vo+|A>!XAk0dRRU;fWz=K={{x_g$L;_C literal 0 HcmV?d00001