2
0
mirror of https://github.com/koreader/koreader synced 2024-10-31 21:20:20 +00:00
koreader/koptreader.lua

337 lines
10 KiB
Lua

require "unireader"
require "inputbox"
require "koptconfig"
Configurable = {
font_size = 1.0,
page_margin = 0.06,
line_spacing = 1.2,
word_spacing = 0.375,
text_wrap = 1,
justification = -1,
full_just = -1,
contrast = 1.0,
}
function Configurable:hash()
hash = self.font_size..'_'..self.page_margin
hash = hash..'_'..self.line_spacing..'_'..self.word_spacing
hash = hash..'_'..self.text_wrap..'_'..self.justification..'_'..self.full_just..'_'..self.contrast
return hash
end
KOPTReader = UniReader:new{}
-- open a PDF/DJVU file and its settings store
function KOPTReader:open(filename)
-- muPDF manages its own cache, set second parameter
-- to the maximum size you want it to grow
local file_type = string.lower(string.match(filename, ".+%.([^.]+)") or "")
if file_type == "pdf" then
local ok
ok, self.doc = pcall(pdf.openDocument, filename, self.cache_document_size)
if not ok then
return false, self.doc -- will contain error message
end
if self.doc:needsPassword() then
local password = InputBox:input(G_height-100, 100, "Pass:")
if not password or not self.doc:authenticatePassword(password) then
self.doc:close()
self.doc = nil
return false, "wrong or missing password"
end
-- password wrong or not entered
end
local ok, err = pcall(self.doc.getPages, self.doc)
if not ok then
-- for PDFs, they might trigger errors later when accessing page tree
self.doc:close()
self.doc = nil
return false, "damaged page tree"
end
return true
elseif file_type == "djvu" then
if not validDJVUFile(filename) then
return false, "Not a valid DjVu file"
end
local ok
ok, self.doc = pcall(djvu.openDocument, filename, self.cache_document_size)
if not ok then
return ok, self.doc -- this will be the error message instead
end
return ok
end
end
function KOPTReader:drawOrCache(no, preCache)
-- our general caching strategy is as follows:
-- #1 goal: we must render the needed area.
-- #2 goal: we render as much of the requested page as we can
-- #3 goal: we render the full page
-- #4 goal: we render next page, too. (TODO)
-- ideally, this should be factored out and only be called when needed (TODO)
local ok, page = pcall(self.doc.openPage, self.doc, no)
local width, height = G_width, G_height
if not ok then
-- TODO: error handling
return nil
end
local dc = self:setzoom(page, preCache)
-- check if we have relevant cache contents
local pagehash = no..'_'..self.kopt_zoom..'_'..self.globalrotate..'_'..self.kopt_gamma..'_'..Configurable:hash()
Debug('page hash', pagehash)
if self.cache[pagehash] ~= nil then
-- we have something in cache
-- requested part is within cached tile
-- ...so properly clean page
page:close()
self.min_offset_x = fb.bb:getWidth() - self.cache[pagehash].w
self.min_offset_y = fb.bb:getHeight() - self.cache[pagehash].h
if(self.min_offset_x > 0) then
self.min_offset_x = 0
end
if(self.min_offset_y > 0) then
self.min_offset_y = 0
end
if self.offset_x < self.min_offset_x then
self.offset_x = self.min_offset_x
end
if self.offset_y < self.min_offset_y then
self.offset_y = self.min_offset_y
end
-- offset_x_in_page & offset_y_in_page is the offset within zoomed page
-- they are always positive.
-- you can see self.offset_x_& self.offset_y as the offset within
-- draw space, which includes the page. So it can be negative and positive.
local offset_x_in_page = -self.offset_x
local offset_y_in_page = -self.offset_y
if offset_x_in_page < 0 then offset_x_in_page = 0 end
if offset_y_in_page < 0 then offset_y_in_page = 0 end
Debug("cached page offset_x",self.offset_x,"offset_y",self.offset_y,"min_offset_x",self.min_offset_x,"min_offset_y",self.min_offset_y)
-- ...and give it more time to live (ttl), except if we're precaching
if not preCache then
self.cache[pagehash].ttl = self.cache_max_ttl
end
-- ...and return blitbuffer plus offset into it
return pagehash,
offset_x_in_page - self.cache[pagehash].x,
offset_y_in_page - self.cache[pagehash].y
end
-- okay, we do not have it in cache yet.
-- so render now.
-- start off with the requested area
local tile = { x = offset_x_in_page, y = offset_y_in_page,
w = width, h = height }
-- can we cache the full page?
local max_cache = self.cache_max_memsize
if preCache then
max_cache = max_cache - self.cache[self.pagehash].size
end
Debug("page::reflowPage:", "width:", width, "height:", height)
local page_margin = Configurable.page_margin
local line_spacing, word_spacing = Configurable.line_spacing, Configurable.word_spacing
local text_wrap, justification, full_just = Configurable.text_wrap, Configurable.justification, Configurable.full_just
local contrast = Configurable.contrast
self.fullwidth, self.fullheight, self.kopt_zoom = page:reflow(dc, self.render_mode, width, height, page_margin, line_spacing, word_spacing, text_wrap, justification, full_just, contrast)
self.globalzoom_orig = self.kopt_zoom
Debug("page::reflowPage:", "fullwidth:", self.fullwidth, "fullheight:", self.fullheight, "zoom:", self.kopt_zoom)
if (self.fullwidth * self.fullheight / 2) <= max_cache then
-- yes we can, so do this with offset 0, 0
tile.x = 0
tile.y = 0
tile.w = self.fullwidth
tile.h = self.fullheight
else
if not preCache then
Debug("ERROR not enough memory in cache left, probably a bug.")
end
return nil
end
self:cacheClaim(tile.w * tile.h / 2);
self.cache[pagehash] = {
x = tile.x,
y = tile.y,
w = tile.w,
h = tile.h,
ttl = self.cache_max_ttl,
size = tile.w * tile.h / 2,
bb = Blitbuffer.new(tile.w, tile.h)
}
--Debug ("new biltbuffer:"..dump(self.cache[pagehash]))
dc:setOffset(-tile.x, -tile.y)
Debug("page::drawReflowedPage:", "rendering page:", no, "width:", self.cache[pagehash].w, "height:", self.cache[pagehash].h)
page:rfdraw(dc, self.cache[pagehash].bb)
page:close()
self.min_offset_x = fb.bb:getWidth() - self.cache[pagehash].w
self.min_offset_y = fb.bb:getHeight() - self.cache[pagehash].h
if(self.min_offset_x > 0) then
self.min_offset_x = 0
end
if(self.min_offset_y > 0) then
self.min_offset_y = 0
end
if self.offset_x < self.min_offset_x then
self.offset_x = self.min_offset_x
end
if self.offset_y < self.min_offset_y then
self.offset_y = self.min_offset_y
end
local offset_x_in_page = -self.offset_x
local offset_y_in_page = -self.offset_y
if offset_x_in_page < 0 then offset_x_in_page = 0 end
if offset_y_in_page < 0 then offset_y_in_page = 0 end
-- return hash and offset within blitbuffer
return pagehash,
offset_x_in_page - tile.x,
offset_y_in_page - tile.y
end
-- set viewer state according to zoom state
function KOPTReader:setzoom(page, preCache)
local dc = DrawContext.new()
if(self.min_offset_x > 0) then
self.min_offset_x = 0
end
if(self.min_offset_y > 0) then
self.min_offset_y = 0
end
dc:setZoom(self.kopt_zoom)
Debug("setzoom:", "globalzoom_orig", self.globalzoom_orig)
if self.kopt_gamma ~= self.GAMMA_NO_GAMMA then
Debug("gamma correction: ", self.kopt_gamma)
dc:setGamma(self.kopt_gamma)
end
return dc
end
-- adjust zoom state and trigger re-rendering
function KOPTReader:setGlobalZoom(zoom)
if self.kopt_zoom ~= zoom then
local last_zoom = self.kopt_zoom
self.kopt_zoom = zoom
-- we will guess the offset_y in zoomed view.
-- it is not a good enough guess but simple enough.
self.offset_x = 0
self.offset_y = self.offset_y*zoom/last_zoom
self:redrawCurrentPage()
end
end
-- adjust global gamma setting
function KOPTReader:modifyGamma(factor)
Debug("modifyGamma, gamma=", self.kopt_gamma, " factor=", factor)
self.kopt_gamma = self.kopt_gamma * factor * factor;
InfoMessage:inform(string.format("New gamma is %.2f", self.kopt_gamma), nil, 1, MSG_AUX)
self:redrawCurrentPage()
end
function KOPTReader:nextView()
local pageno = self.pageno
Debug("nextView offset_y", self.offset_y, "min_offset_y", self.min_offset_y)
if self.offset_y <= self.min_offset_y then
-- hit content bottom, turn to next page top
self.offset_x = 0
self.offset_y = 0
pageno = pageno + 1
else
-- goto next view of current page
local offset_y_dec = self.offset_y - G_height + self.pan_overlap_vertical
self.offset_y = offset_y_dec > self.min_offset_y and offset_y_dec or self.min_offset_y
end
return pageno
end
function KOPTReader:prevView()
local pageno = self.pageno
Debug("preView offset_y", self.offset_y, "min_offset_y", self.min_offset_y)
if self.offset_y >= 0 then
-- hit content top, turn to previous page bottom
self.offset_x = 0
self.offset_y = -2012534
pageno = pageno - 1
else
-- goto previous view of current page
local offset_y_inc = self.offset_y + G_height - self.pan_overlap_vertical
self.offset_y = offset_y_inc < 0 and offset_y_inc or 0
end
return pageno
end
function KOPTReader:setDefaults()
self.show_overlap_enable = true
self.show_links_enable = false
end
-- backup global variables from UniReader
function KOPTReader:loadSettings(filename)
UniReader.loadSettings(self,filename)
self.kopt_zoom = self.settings:readSetting("kopt_zoom") or 1.0
self.kopt_gamma = self.settings:readSetting("kopt_gamma") or 1.0
end
function KOPTReader:saveSpecialSettings()
self.settings:saveSetting("kopt_zoom", self.kopt_zoom)
self.settings:saveSetting("kopt_gamma", self.kopt_gamma)
end
function KOPTReader:init()
self:addAllCommands()
self:adjustCommands()
end
function KOPTReader:adjustCommands()
self.commands:del(KEY_A, nil,"A")
self.commands:del(KEY_A, MOD_SHIFT, "A")
self.commands:del(KEY_D, nil,"D")
self.commands:del(KEY_D, MOD_SHIFT, "D")
self.commands:del(KEY_S, nil,"S")
self.commands:del(KEY_S, MOD_SHIFT, "S")
self.commands:del(KEY_F, nil,"F")
self.commands:del(KEY_F, MOD_SHIFT, "F")
self.commands:del(KEY_Z, nil,"Z")
self.commands:del(KEY_Z, MOD_ALT, "Z")
self.commands:del(KEY_Z, MOD_SHIFT, "Z")
self.commands:del(KEY_X, nil,"X")
self.commands:del(KEY_X, MOD_SHIFT, "X")
self.commands:del(KEY_N, nil,"N")
self.commands:del(KEY_N, MOD_SHIFT, "N")
self.commands:del(KEY_L, nil, "L")
self.commands:del(KEY_L, MOD_SHIFT, "L")
self.commands:del(KEY_M, nil, "M")
self.commands:add({KEY_F,KEY_AA}, nil, "F",
"change koptreader configuration",
function(self)
KOPTConfig:config(KOPTReader.redrawCurrentPage, self, Configurable)
self:redrawCurrentPage()
end
)
end