mirror of
https://github.com/koreader/koreader
synced 2024-11-13 19:11:25 +00:00
495accfec9
CRE cache, hyphdict and fonts can be initialized only once when first credocument is opened. Previously, they were recreated for each document, and as previous instances were probably not free'd, this caused memory leaks.
492 lines
16 KiB
Lua
492 lines
16 KiB
Lua
local Blitbuffer = require("ffi/blitbuffer")
|
|
local CreOptions = require("ui/data/creoptions")
|
|
local DataStorage = require("datastorage")
|
|
local Document = require("document/document")
|
|
local Font = require("ui/font")
|
|
local Geom = require("ui/geometry")
|
|
local Screen = require("device").screen
|
|
local ffi = require("ffi")
|
|
local lfs = require("libs/libkoreader-lfs")
|
|
local logger = require("logger")
|
|
|
|
-- engine can be initialized only once, on first document opened
|
|
local engine_initialized = false
|
|
|
|
local CreDocument = Document:new{
|
|
-- this is defined in kpvcrlib/crengine/crengine/include/lvdocview.h
|
|
SCROLL_VIEW_MODE = 0,
|
|
PAGE_VIEW_MODE = 1,
|
|
|
|
_document = false,
|
|
_loaded = false,
|
|
|
|
line_space_percent = 100,
|
|
default_font = G_reader_settings:readSetting("cre_font") or "Noto Serif",
|
|
header_font = G_reader_settings:readSetting("header_font") or "Noto Sans",
|
|
fallback_font = G_reader_settings:readSetting("fallback_font") or "Noto Sans CJK SC",
|
|
default_css = "./data/cr3.css",
|
|
options = CreOptions,
|
|
}
|
|
|
|
-- NuPogodi, 20.05.12: inspect the zipfile content
|
|
function CreDocument:zipContentExt(fname)
|
|
local std_out = io.popen("unzip ".."-qql \""..fname.."\"")
|
|
if std_out then
|
|
for line in std_out:lines() do
|
|
local size, ext = string.match(line, "%s+(%d+)%s+.+%.([^.]+)")
|
|
-- return the extention
|
|
if size and ext then return string.lower(ext) end
|
|
end
|
|
end
|
|
end
|
|
|
|
function CreDocument:cacheInit()
|
|
-- remove legacy cr3cache directory
|
|
if lfs.attributes("./cr3cache", "mode") == "directory" then
|
|
os.execute("rm -r ./cr3cache")
|
|
end
|
|
cre.initCache(DataStorage:getDataDir() .. "/cache/cr3cache", 1024*1024*32)
|
|
end
|
|
|
|
function CreDocument:engineInit()
|
|
if not engine_initialized then
|
|
require "libs/libkoreader-cre"
|
|
-- initialize cache
|
|
self:cacheInit()
|
|
|
|
-- initialize hyph dictionaries
|
|
cre.initHyphDict("./data/hyph/")
|
|
|
|
-- we need to initialize the CRE font list
|
|
local fonts = Font:getFontList()
|
|
for _k, _v in ipairs(fonts) do
|
|
if not _v:find("/urw/") then
|
|
local ok, err = pcall(cre.registerFont, _v)
|
|
if not ok then
|
|
logger.err("failed to register crengine font", err)
|
|
end
|
|
end
|
|
end
|
|
|
|
engine_initialized = true
|
|
end
|
|
end
|
|
|
|
function CreDocument:init()
|
|
self:engineInit()
|
|
self.configurable:loadDefaults(self.options)
|
|
|
|
local file_type = string.lower(string.match(self.file, ".+%.([^.]+)"))
|
|
if file_type == "zip" then
|
|
-- NuPogodi, 20.05.12: read the content of zip-file
|
|
-- and return extention of the 1st file
|
|
file_type = self:zipContentExt(self.file) or "unknown"
|
|
end
|
|
-- these two format use the same css file
|
|
if file_type == "html" then
|
|
file_type = "htm"
|
|
end
|
|
-- if native css-file doesn't exist, one needs to use default cr3.css
|
|
if not io.open("./data/"..file_type..".css") then
|
|
file_type = "cr3"
|
|
end
|
|
self.default_css = "./data/"..file_type..".css"
|
|
|
|
-- @TODO check the default view_mode to a global user configurable
|
|
-- variable 22.12 2012 (houqp)
|
|
local ok
|
|
ok, self._document = pcall(cre.newDocView,
|
|
Screen:getWidth(), Screen:getHeight(), self.PAGE_VIEW_MODE
|
|
)
|
|
if not ok then
|
|
error(self._document) -- will contain error message
|
|
end
|
|
|
|
-- adjust font sizes according to screen dpi
|
|
self._document:adjustFontSizes(Screen:getDPI())
|
|
|
|
-- set fallback font face
|
|
self._document:setStringProperty("crengine.font.fallback.face", self.fallback_font)
|
|
|
|
self.is_open = true
|
|
self.info.has_pages = false
|
|
self:_readMetadata()
|
|
self.info.configurable = true
|
|
end
|
|
|
|
function CreDocument:loadDocument()
|
|
if not self._loaded then
|
|
self._document:loadDocument(self.file)
|
|
self._loaded = true
|
|
end
|
|
end
|
|
|
|
function CreDocument:render()
|
|
-- load document before rendering
|
|
self:loadDocument()
|
|
-- set visible page count in landscape
|
|
if math.max(Screen:getWidth(), Screen:getHeight()) / Screen:getDPI()
|
|
< DCREREADER_TWO_PAGE_THRESHOLD then
|
|
self:setVisiblePageCount(1)
|
|
end
|
|
self._document:renderDocument()
|
|
if not self.info.has_pages then
|
|
self.info.doc_height = self._document:getFullHeight()
|
|
end
|
|
end
|
|
|
|
function CreDocument:close()
|
|
Document.close(self)
|
|
end
|
|
|
|
function CreDocument:getPageCount()
|
|
return self._document:getPages()
|
|
end
|
|
|
|
function CreDocument:getCoverPageImage()
|
|
-- don't need to render document in order to get cover image
|
|
self:loadDocument()
|
|
local data, size = self._document:getCoverPageImageData()
|
|
if data and size then
|
|
local Mupdf = require("ffi/mupdf")
|
|
local ok, image = pcall(Mupdf.renderImage, data, size)
|
|
ffi.C.free(data)
|
|
if ok then
|
|
return image
|
|
end
|
|
end
|
|
end
|
|
|
|
function CreDocument:getImageFromPosition(pos)
|
|
local data, size = self._document:getImageDataFromPosition(pos.x, pos.y)
|
|
if data and size then
|
|
logger.dbg("CreDocument: got image data from position", data, size)
|
|
local Mupdf = require("ffi/mupdf")
|
|
-- wrapped with pcall so we always free(data)
|
|
local ok, image = pcall(Mupdf.renderImage, data, size)
|
|
logger.dbg("Mupdf.renderImage", ok, image)
|
|
if not ok and string.find(image, "could not load image data: unknown image file format") then
|
|
-- in that case, mupdf seems to have already freed data (see mupdf/source/fitz/image.c:494),
|
|
-- as doing outselves ffi.C.free(data) would result in a crash with :
|
|
-- *** Error in `./luajit': double free or corruption (!prev): 0x0000000000e48a40 ***
|
|
logger.warn("Mupdf says 'unknown image file format', assuming mupdf has already freed image data")
|
|
else
|
|
ffi.C.free(data) -- need that explicite clean
|
|
end
|
|
if ok then
|
|
return image
|
|
end
|
|
end
|
|
end
|
|
|
|
function CreDocument:getWordFromPosition(pos)
|
|
local word_box = self._document:getWordFromPosition(pos.x, pos.y)
|
|
logger.dbg("CreDocument: get word box", word_box)
|
|
local text_range = self._document:getTextFromPositions(pos.x, pos.y, pos.x, pos.y)
|
|
logger.dbg("CreDocument: get text range", text_range)
|
|
local wordbox = {
|
|
word = text_range.text == "" and word_box.word or text_range.text,
|
|
page = self._document:getCurrentPage(),
|
|
}
|
|
if word_box.word then
|
|
wordbox.sbox = Geom:new{
|
|
x = word_box.x0, y = word_box.y0,
|
|
w = word_box.x1 - word_box.x0,
|
|
h = word_box.y1 - word_box.y0,
|
|
}
|
|
else
|
|
-- dummy word box
|
|
wordbox.sbox = Geom:new{
|
|
x = pos.x, y = pos.y,
|
|
w = 20, h = 20,
|
|
}
|
|
end
|
|
return wordbox
|
|
end
|
|
|
|
function CreDocument:getTextFromPositions(pos0, pos1)
|
|
local text_range = self._document:getTextFromPositions(pos0.x, pos0.y, pos1.x, pos1.y)
|
|
logger.dbg("CreDocument: get text range", text_range)
|
|
if text_range then
|
|
-- local line_boxes = self:getScreenBoxesFromPositions(text_range.pos0, text_range.pos1)
|
|
return {
|
|
text = text_range.text,
|
|
pos0 = text_range.pos0,
|
|
pos1 = text_range.pos1,
|
|
--sboxes = line_boxes, -- boxes on screen
|
|
}
|
|
end
|
|
end
|
|
|
|
function CreDocument:getScreenBoxesFromPositions(pos0, pos1)
|
|
local line_boxes = {}
|
|
if pos0 and pos1 then
|
|
local word_boxes = self._document:getWordBoxesFromPositions(pos0, pos1)
|
|
for i = 1, #word_boxes do
|
|
local line_box = word_boxes[i]
|
|
table.insert(line_boxes, Geom:new{
|
|
x = line_box.x0, y = line_box.y0,
|
|
w = line_box.x1 - line_box.x0,
|
|
h = line_box.y1 - line_box.y0,
|
|
})
|
|
end
|
|
end
|
|
return line_boxes
|
|
end
|
|
|
|
function CreDocument:drawCurrentView(target, x, y, rect, pos)
|
|
if self.buffer and (self.buffer.w ~= rect.w or self.buffer.h ~= rect.h) then
|
|
self.buffer:free()
|
|
self.buffer = nil
|
|
end
|
|
if not self.buffer then
|
|
self.buffer = Blitbuffer.new(rect.w, rect.h)
|
|
end
|
|
self._document:drawCurrentPage(self.buffer)
|
|
target:blitFrom(self.buffer, x, y, 0, 0, rect.w, rect.h)
|
|
end
|
|
|
|
function CreDocument:drawCurrentViewByPos(target, x, y, rect, pos)
|
|
self._document:gotoPos(pos)
|
|
self:drawCurrentView(target, x, y, rect)
|
|
end
|
|
|
|
function CreDocument:drawCurrentViewByPage(target, x, y, rect, page)
|
|
self._document:gotoPage(page)
|
|
self:drawCurrentView(target, x, y, rect)
|
|
end
|
|
|
|
function CreDocument:hintPage(pageno, zoom, rotation)
|
|
end
|
|
|
|
function CreDocument:drawPage(target, x, y, rect, pageno, zoom, rotation)
|
|
end
|
|
|
|
function CreDocument:renderPage(pageno, rect, zoom, rotation)
|
|
end
|
|
|
|
function CreDocument:gotoXPointer(xpointer)
|
|
logger.dbg("CreDocument: goto xpointer", xpointer)
|
|
self._document:gotoXPointer(xpointer)
|
|
end
|
|
|
|
function CreDocument:getXPointer()
|
|
return self._document:getXPointer()
|
|
end
|
|
|
|
function CreDocument:isXPointerInDocument(xp)
|
|
return self._document:isXPointerInDocument(xp)
|
|
end
|
|
|
|
function CreDocument:getPosFromXPointer(xp)
|
|
return self._document:getPosFromXPointer(xp)
|
|
end
|
|
|
|
function CreDocument:getPageFromXPointer(xp)
|
|
return self._document:getPageFromXPointer(xp)
|
|
end
|
|
|
|
function CreDocument:getFontFace()
|
|
return self._document:getFontFace()
|
|
end
|
|
|
|
function CreDocument:getCurrentPos()
|
|
return self._document:getCurrentPos()
|
|
end
|
|
|
|
function CreDocument:getPageLinks()
|
|
return self._document:getPageLinks()
|
|
end
|
|
|
|
function CreDocument:getLinkFromPosition(pos)
|
|
return self._document:getLinkFromPosition(pos.x, pos.y)
|
|
end
|
|
|
|
function Document:gotoPos(pos)
|
|
logger.dbg("CreDocument: goto position", pos)
|
|
self._document:gotoPos(pos)
|
|
end
|
|
|
|
function CreDocument:gotoPage(page)
|
|
logger.dbg("CreDocument: goto page", page)
|
|
self._document:gotoPage(page)
|
|
end
|
|
|
|
function CreDocument:gotoLink(link)
|
|
logger.dbg("CreDocument: goto link", link)
|
|
self._document:gotoLink(link)
|
|
end
|
|
|
|
function CreDocument:goBack()
|
|
logger.dbg("CreDocument: go back")
|
|
self._document:goBack()
|
|
end
|
|
|
|
function CreDocument:goForward(link)
|
|
logger.dbg("CreDocument: go forward")
|
|
self._document:goForward()
|
|
end
|
|
|
|
function CreDocument:getCurrentPage()
|
|
return self._document:getCurrentPage()
|
|
end
|
|
|
|
function CreDocument:setFontFace(new_font_face)
|
|
if new_font_face then
|
|
logger.dbg("CreDocument: set font face", new_font_face)
|
|
self._document:setStringProperty("font.face.default", new_font_face)
|
|
end
|
|
end
|
|
|
|
function CreDocument:setHyphDictionary(new_hyph_dictionary)
|
|
if new_hyph_dictionary then
|
|
logger.dbg("CreDocument: set hyphenation dictionary", new_hyph_dictionary)
|
|
self._document:setStringProperty("crengine.hyphenation.directory", new_hyph_dictionary)
|
|
end
|
|
end
|
|
|
|
function CreDocument:clearSelection()
|
|
logger.dbg("clear selection")
|
|
self._document:clearSelection()
|
|
end
|
|
|
|
function CreDocument:getFontSize()
|
|
return self._document:getFontSize()
|
|
end
|
|
|
|
function CreDocument:setFontSize(new_font_size)
|
|
if new_font_size then
|
|
logger.dbg("CreDocument: set font size", new_font_size)
|
|
self._document:setFontSize(new_font_size)
|
|
end
|
|
end
|
|
|
|
function CreDocument:setViewMode(new_mode)
|
|
if new_mode then
|
|
logger.dbg("CreDocument: set view mode", new_mode)
|
|
if new_mode == "scroll" then
|
|
self._document:setViewMode(self.SCROLL_VIEW_MODE)
|
|
else
|
|
self._document:setViewMode(self.PAGE_VIEW_MODE)
|
|
end
|
|
end
|
|
end
|
|
|
|
function CreDocument:setViewDimen(dimen)
|
|
logger.dbg("CreDocument: set view dimen", dimen)
|
|
self._document:setViewDimen(dimen.w, dimen.h)
|
|
end
|
|
|
|
function CreDocument:setHeaderFont(new_font)
|
|
if new_font then
|
|
logger.dbg("CreDocument: set header font", new_font)
|
|
self._document:setHeaderFont(new_font)
|
|
end
|
|
end
|
|
|
|
function CreDocument:zoomFont(delta)
|
|
logger.dbg("CreDocument: zoom font", delta)
|
|
self._document:zoomFont(delta)
|
|
end
|
|
|
|
function CreDocument:setInterlineSpacePercent(percent)
|
|
logger.dbg("CreDocument: set interline space", percent)
|
|
self._document:setDefaultInterlineSpace(percent)
|
|
end
|
|
|
|
function CreDocument:toggleFontBolder(toggle)
|
|
logger.dbg("CreDocument: toggle font bolder", toggle)
|
|
self._document:setIntProperty("font.face.weight.embolden", toggle)
|
|
end
|
|
|
|
function CreDocument:setGammaIndex(index)
|
|
logger.dbg("CreDocument: set gamma index", index)
|
|
cre.setGammaIndex(index)
|
|
end
|
|
|
|
function CreDocument:setStyleSheet(new_css)
|
|
logger.dbg("CreDocument: set style sheet", new_css)
|
|
self._document:setStyleSheet(new_css)
|
|
end
|
|
|
|
function CreDocument:setEmbeddedStyleSheet(toggle)
|
|
-- FIXME: occasional segmentation fault when switching embedded style sheet
|
|
logger.dbg("CreDocument: set embedded style sheet", toggle)
|
|
self._document:setIntProperty("crengine.doc.embedded.styles.enabled", toggle)
|
|
end
|
|
|
|
function CreDocument:setPageMargins(left, top, right, bottom)
|
|
logger.dbg("CreDocument: set page margins", left, top, right, bottom)
|
|
self._document:setIntProperty("crengine.page.margin.left", left)
|
|
self._document:setIntProperty("crengine.page.margin.top", top)
|
|
self._document:setIntProperty("crengine.page.margin.right", right)
|
|
self._document:setIntProperty("crengine.page.margin.bottom", bottom)
|
|
end
|
|
|
|
function CreDocument:setFloatingPunctuation(enabled)
|
|
-- FIXME: occasional segmentation fault when toggling floating punctuation
|
|
logger.dbg("CreDocument: set floating punctuation", enabled)
|
|
self._document:setIntProperty("crengine.style.floating.punctuation.enabled", enabled)
|
|
end
|
|
|
|
function CreDocument:setTxtPreFormatted(enabled)
|
|
logger.dbg("CreDocument: set txt preformatted", enabled)
|
|
self._document:setIntProperty("crengine.file.txt.preformatted", enabled)
|
|
end
|
|
|
|
function CreDocument:getVisiblePageCount()
|
|
return self._document:getVisiblePageCount()
|
|
end
|
|
|
|
function CreDocument:setVisiblePageCount(new_count)
|
|
logger.dbg("CreDocument: set visible page count", new_count)
|
|
self._document:setVisiblePageCount(new_count)
|
|
end
|
|
|
|
function CreDocument:setBatteryState(state)
|
|
logger.dbg("CreDocument: set battery state", state)
|
|
self._document:setBatteryState(state)
|
|
end
|
|
|
|
function CreDocument:isXPointerInCurrentPage(xp)
|
|
logger.dbg("CreDocument: check xpointer in current page", xp)
|
|
return self._document:isXPointerInCurrentPage(xp)
|
|
end
|
|
|
|
function CreDocument:setStatusLineProp(prop)
|
|
logger.dbg("CreDocument: set status line property", prop)
|
|
self._document:setStringProperty("window.status.line", prop)
|
|
end
|
|
|
|
function CreDocument:findText(pattern, origin, reverse, caseInsensitive)
|
|
logger.dbg("CreDocument: find text", pattern, origin, reverse, caseInsensitive)
|
|
return self._document:findText(
|
|
pattern, origin, reverse, caseInsensitive and 1 or 0)
|
|
end
|
|
|
|
function CreDocument:register(registry)
|
|
registry:addProvider("azw", "application/azw", self)
|
|
registry:addProvider("epub", "application/epub", self)
|
|
registry:addProvider("chm", "application/chm", self)
|
|
registry:addProvider("doc", "application/doc", self)
|
|
registry:addProvider("fb2", "application/fb2", self)
|
|
registry:addProvider("fb2.zip", "application/zip", self)
|
|
registry:addProvider("html", "application/html", self)
|
|
registry:addProvider("html.zip", "application/zip", self)
|
|
registry:addProvider("htm", "application/htm", self)
|
|
registry:addProvider("htm.zip", "application/zip", self)
|
|
registry:addProvider("log", "text/plain", self)
|
|
registry:addProvider("log.zip", "application/zip", self)
|
|
registry:addProvider("md", "text/plain", self)
|
|
registry:addProvider("md.zip", "application/zip", self)
|
|
registry:addProvider("mobi", "application/mobi", self)
|
|
registry:addProvider("pdb", "application/pdb", self)
|
|
registry:addProvider("prc", "application/prc", self)
|
|
registry:addProvider("tcr", "application/tcr", self)
|
|
registry:addProvider("txt", "text/plain", self)
|
|
registry:addProvider("txt.zip", "application/zip", self)
|
|
registry:addProvider("rtf", "application/rtf", self)
|
|
end
|
|
|
|
return CreDocument
|