Merge pull request #85 from chrox/text_highlight

add text highlight in both reflow and non-reflow mode
pull/86/head
{Qingping,Dave} Hou 11 years ago
commit d934de890d

@ -66,7 +66,10 @@ customupdate: all
cp -p README.md COPYING $(KOR_BASE)/{koreader-base,extr} koreader.sh $(LUA_FILES) $(INSTALL_DIR)
$(STRIP) --strip-unneeded $(INSTALL_DIR)/koreader-base $(INSTALL_DIR)/extr
mkdir $(INSTALL_DIR)/data
cp -L koreader-base/$(DJVULIB) $(KOR_BASE)/$(CRELIB) $(KOR_BASE)/$(LUALIB) $(KOR_BASE)/$(K2PDFOPTLIB) $(INSTALL_DIR)/libs
cp -L koreader-base/$(DJVULIB) $(KOR_BASE)/$(CRELIB) \
$(KOR_BASE)/$(LUALIB) $(KOR_BASE)/$(K2PDFOPTLIB) \
$(KOR_BASE)/$(LEPTONICALIB) $(KOR_BASE)/$(TESSERACTLIB) \
$(INSTALL_DIR)/libs
$(STRIP) --strip-unneeded $(INSTALL_DIR)/libs/*
cp -rpL $(KOR_BASE)/data/*.css $(INSTALL_DIR)/data
cp -rpL $(KOR_BASE)/fonts $(INSTALL_DIR)

@ -0,0 +1,855 @@
-- -*- coding: utf-8 -*-
--
-- Copyright 2010-2012 Jeffrey Friedl
-- http://regex.info/blog/
--
local VERSION = 20111207.5 -- version history at end of file
local OBJDEF = { VERSION = VERSION }
--
-- Simple JSON encoding and decoding in pure Lua.
-- http://www.json.org/
--
--
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines
--
-- local lua_value = JSON:decode(raw_json_text)
--
-- local raw_json_text = JSON:encode(lua_table_or_value)
-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
--
--
-- DECODING
--
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines
--
-- local lua_value = JSON:decode(raw_json_text)
--
-- If the JSON text is for an object or an array, e.g.
-- { "what": "books", "count": 3 }
-- or
-- [ "Larry", "Curly", "Moe" ]
--
-- the result is a Lua table, e.g.
-- { what = "books", count = 3 }
-- or
-- { "Larry", "Curly", "Moe" }
--
--
-- The encode and decode routines accept an optional second argument, "etc", which is not used
-- during encoding or decoding, but upon error is passed along to error handlers. It can be of any
-- type (including nil).
--
-- With most errors during decoding, this code calls
--
-- JSON:onDecodeError(message, text, location, etc)
--
-- with a message about the error, and if known, the JSON text being parsed and the byte count
-- where the problem was discovered. You can replace the default JSON:onDecodeError() with your
-- own function.
--
-- The default onDecodeError() merely augments the message with data about the text and the
-- location if known (and if a second 'etc' argument had been provided to decode(), its value is
-- tacked onto the message as well), and then calls JSON.assert(), which itself defaults to Lua's
-- built-in assert(), and can also be overridden.
--
-- For example, in an Adobe Lightroom plugin, you might use something like
--
-- function JSON:onDecodeError(message, text, location, etc)
-- LrErrors.throwUserError("Internal Error: invalid JSON data")
-- end
--
-- or even just
--
-- function JSON.assert(message)
-- LrErrors.throwUserError("Internal Error: " .. message)
-- end
--
-- If JSON:decode() is passed a nil, this is called instead:
--
-- JSON:onDecodeOfNilError(message, nil, nil, etc)
--
-- and if JSON:decode() is passed HTML instead of JSON, this is called:
--
-- JSON:onDecodeOfHTMLError(message, text, nil, etc)
--
-- The use of the fourth 'etc' argument allows stronger coordination between decoding and error
-- reporting, especially when you provide your own error-handling routines. Continuing with the
-- the Adobe Lightroom plugin example:
--
-- function JSON:onDecodeError(message, text, location, etc)
-- local note = "Internal Error: invalid JSON data"
-- if type(etc) = 'table' and etc.photo then
-- note = note .. " while processing for " .. etc.photo:getFormattedMetadata('fileName')
-- end
-- LrErrors.throwUserError(note)
-- end
--
-- :
-- :
--
-- for i, photo in ipairs(photosToProcess) do
-- :
-- :
-- local data = JSON:decode(someJsonText, { photo = photo })
-- :
-- :
-- end
--
--
--
--
-- DECODING AND STRICT TYPES
--
-- Because both JSON objects and JSON arrays are converted to Lua tables, it's not normally
-- possible to tell which a Lua table came from, or guarantee decode-encode round-trip
-- equivalency.
--
-- However, if you enable strictTypes, e.g.
--
-- JSON = (loadfile "JSON.lua")() --load the routines
-- JSON.strictTypes = true
--
-- then the Lua table resulting from the decoding of a JSON object or JSON array is marked via Lua
-- metatable, so that when re-encoded with JSON:encode() it ends up as the appropriate JSON type.
--
-- (This is not the default because other routines may not work well with tables that have a
-- metatable set, for example, Lightroom API calls.)
--
--
-- ENCODING
--
-- JSON = (loadfile "JSON.lua")() -- one-time load of the routines
--
-- local raw_json_text = JSON:encode(lua_table_or_value)
-- local pretty_json_text = JSON:encode_pretty(lua_table_or_value) -- "pretty printed" version for human readability
-- On error during encoding, this code calls:
--
-- JSON:onEncodeError(message, etc)
--
-- which you can override in your local JSON object.
--
--
-- SUMMARY OF METHODS YOU CAN OVERRIDE IN YOUR LOCAL LUA JSON OBJECT
--
-- assert
-- onDecodeError
-- onDecodeOfNilError
-- onDecodeOfHTMLError
-- onEncodeError
--
-- If you want to create a separate Lua JSON object with its own error handlers,
-- you can reload JSON.lua or use the :new() method.
--
---------------------------------------------------------------------------
local author = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json), version " .. tostring(VERSION) .. " ]-"
local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray
local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject
function OBJDEF:newArray(tbl)
return setmetatable(tbl or {}, isArray)
end
function OBJDEF:newObject(tbl)
return setmetatable(tbl or {}, isObject)
end
local function unicode_codepoint_as_utf8(codepoint)
--
-- codepoint is a number
--
if codepoint <= 127 then
return string.char(codepoint)
elseif codepoint <= 2047 then
--
-- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8
--
local highpart = math.floor(codepoint / 0x40)
local lowpart = codepoint - (0x40 * highpart)
return string.char(0xC0 + highpart,
0x80 + lowpart)
elseif codepoint <= 65535 then
--
-- 1110yyyy 10yyyyxx 10xxxxxx
--
local highpart = math.floor(codepoint / 0x1000)
local remainder = codepoint - 0x1000 * highpart
local midpart = math.floor(remainder / 0x40)
local lowpart = remainder - 0x40 * midpart
highpart = 0xE0 + highpart
midpart = 0x80 + midpart
lowpart = 0x80 + lowpart
--
-- Check for an invalid character (thanks Andy R. at Adobe).
-- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070
--
if ( highpart == 0xE0 and midpart < 0xA0 ) or
( highpart == 0xED and midpart > 0x9F ) or
( highpart == 0xF0 and midpart < 0x90 ) or
( highpart == 0xF4 and midpart > 0x8F )
then
return "?"
else
return string.char(highpart,
midpart,
lowpart)
end
else
--
-- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
--
local highpart = math.floor(codepoint / 0x40000)
local remainder = codepoint - 0x40000 * highpart
local midA = math.floor(remainder / 0x1000)
remainder = remainder - 0x1000 * midA
local midB = math.floor(remainder / 0x40)
local lowpart = remainder - 0x40 * midB
return string.char(0xF0 + highpart,
0x80 + midA,
0x80 + midB,
0x80 + lowpart)
end
end
function OBJDEF:onDecodeError(message, text, location, etc)
if text then
if location then
message = string.format("%s at char %d of: %s", message, location, text)
else
message = string.format("%s: %s", message, text)
end
end
if etc ~= nil then
message = message .. " (" .. OBJDEF:encode(etc) .. ")"
end
if self.assert then
self.assert(false, message)
else
assert(false, message)
end
end
OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError
OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError
function OBJDEF:onEncodeError(message, etc)
if etc ~= nil then
message = message .. " (" .. OBJDEF:encode(etc) .. ")"
end
if self.assert then
self.assert(false, message)
else
assert(false, message)
end
end
local function grok_number(self, text, start, etc)
--
-- Grab the integer part
--
local integer_part = text:match('^-?[1-9]%d*', start)
or text:match("^-?0", start)
if not integer_part then
self:onDecodeError("expected number", text, start, etc)
end
local i = start + integer_part:len()
--
-- Grab an optional decimal part
--
local decimal_part = text:match('^%.%d+', i) or ""
i = i + decimal_part:len()
--
-- Grab an optional exponential part
--
local exponent_part = text:match('^[eE][-+]?%d+', i) or ""
i = i + exponent_part:len()
local full_number_text = integer_part .. decimal_part .. exponent_part
local as_number = tonumber(full_number_text)
if not as_number then
self:onDecodeError("bad number", text, start, etc)
end
return as_number, i
end
local function grok_string(self, text, start, etc)
if text:sub(start,start) ~= '"' then
self:onDecodeError("expected string's opening quote", text, start, etc)
end
local i = start + 1 -- +1 to bypass the initial quote
local text_len = text:len()
local VALUE = ""
while i <= text_len do
local c = text:sub(i,i)
if c == '"' then
return VALUE, i + 1
end
if c ~= '\\' then
VALUE = VALUE .. c
i = i + 1
elseif text:match('^\\b', i) then
VALUE = VALUE .. "\b"
i = i + 2
elseif text:match('^\\f', i) then
VALUE = VALUE .. "\f"
i = i + 2
elseif text:match('^\\n', i) then
VALUE = VALUE .. "\n"
i = i + 2
elseif text:match('^\\r', i) then
VALUE = VALUE .. "\r"
i = i + 2
elseif text:match('^\\t', i) then
VALUE = VALUE .. "\t"
i = i + 2
else
local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
if hex then
i = i + 6 -- bypass what we just read
-- We have a Unicode codepoint. It could be standalone, or if in the proper range and
-- followed by another in a specific range, it'll be a two-code surrogate pair.
local codepoint = tonumber(hex, 16)
if codepoint >= 0xD800 and codepoint <= 0xDBFF then
-- it's a hi surrogate... see whether we have a following low
local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i)
if lo_surrogate then
i = i + 6 -- bypass the low surrogate we just read
codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)
else
-- not a proper low, so we'll just leave the first codepoint as is and spit it out.
end
end
VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)
else
-- just pass through what's escaped
VALUE = VALUE .. text:match('^\\(.)', i)
i = i + 2
end
end
end
self:onDecodeError("unclosed string", text, start, etc)
end
local function skip_whitespace(text, start)
local match_start, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2
if match_end then
return match_end + 1
else
return start
end
end
local grok_one -- assigned later
local function grok_object(self, text, start, etc)
if not text:sub(start,start) == '{' then
self:onDecodeError("expected '{'", text, start, etc)
end
local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'
local VALUE = self.strictTypes and self:newObject { } or { }
if text:sub(i,i) == '}' then
return VALUE, i + 1
end
local text_len = text:len()
while i <= text_len do
local key, new_i = grok_string(self, text, i, etc)
i = skip_whitespace(text, new_i)
if text:sub(i, i) ~= ':' then
self:onDecodeError("expected colon", text, i, etc)
end
i = skip_whitespace(text, i + 1)
local val, new_i = grok_one(self, text, i)
VALUE[key] = val
--
-- Expect now either '}' to end things, or a ',' to allow us to continue.
--
i = skip_whitespace(text, new_i)
local c = text:sub(i,i)
if c == '}' then
return VALUE, i + 1
end
if text:sub(i, i) ~= ',' then
self:onDecodeError("expected comma or '}'", text, i, etc)
end
i = skip_whitespace(text, i + 1)
end
self:onDecodeError("unclosed '{'", text, start, etc)
end
local function grok_array(self, text, start, etc)
if not text:sub(start,start) == '[' then
self:onDecodeError("expected '['", text, start, etc)
end
local i = skip_whitespace(text, start + 1) -- +1 to skip the '['
local VALUE = self.strictTypes and self:newArray { } or { }
if text:sub(i,i) == ']' then
return VALUE, i + 1
end
local text_len = text:len()
while i <= text_len do
local val, new_i = grok_one(self, text, i)
table.insert(VALUE, val)
i = skip_whitespace(text, new_i)
--
-- Expect now either ']' to end things, or a ',' to allow us to continue.
--
local c = text:sub(i,i)
if c == ']' then
return VALUE, i + 1
end
if text:sub(i, i) ~= ',' then
self:onDecodeError("expected comma or '['", text, i, etc)
end
i = skip_whitespace(text, i + 1)
end
self:onDecodeError("unclosed '['", text, start, etc)
end
grok_one = function(self, text, start, etc)
-- Skip any whitespace
start = skip_whitespace(text, start)
if start > text:len() then
self:onDecodeError("unexpected end of string", text, nil, etc)
end
if text:find('^"', start) then
return grok_string(self, text, start, etc)
elseif text:find('^[-0123456789 ]', start) then
return grok_number(self, text, start, etc)
elseif text:find('^%{', start) then
return grok_object(self, text, start, etc)
elseif text:find('^%[', start) then
return grok_array(self, text, start, etc)
elseif text:find('^true', start) then
return true, start + 4
elseif text:find('^false', start) then
return false, start + 5
elseif text:find('^null', start) then
return nil, start + 4
else
self:onDecodeError("can't parse JSON", text, start, etc)
end
end
function OBJDEF:decode(text, etc)
if type(self) ~= 'table' or self.__index ~= OBJDEF then
OBJDEF:onDecodeError("JSON:decode must be called in method format", nil, nil, etc)
end
if text == nil then
self:onDecodeOfNilError(string.format("nil passed to JSON:decode()"), nil, nil, etc)
elseif type(text) ~= 'string' then
self:onDecodeError(string.format("expected string argument to JSON:decode(), got %s", type(text)), nil, nil, etc)
end
if text:match('^%s*$') then
return nil
end
if text:match('^%s*<') then
-- Can't be JSON... we'll assume it's HTML
self:onDecodeOfHTMLError(string.format("html passed to JSON:decode()"), text, nil, etc)
end
--
-- Ensure that it's not UTF-32 or UTF-16.
-- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3),
-- but this package can't handle them.
--
if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then
self:onDecodeError("JSON package groks only UTF-8, sorry", text, nil, etc)
end
local success, value = pcall(grok_one, self, text, 1, etc)
if success then
return value
else
-- should never get here... JSON parse errors should have been caught earlier
assert(false, value)
return nil
end
end
local function backslash_replacement_function(c)
if c == "\n" then
return "\\n"
elseif c == "\r" then
return "\\r"
elseif c == "\t" then
return "\\t"
elseif c == "\b" then
return "\\b"
elseif c == "\f" then
return "\\f"
elseif c == '"' then
return '\\"'
elseif c == '\\' then
return '\\\\'
else
return string.format("\\u%04x", c:byte())
end
end
local chars_to_be_escaped_in_JSON_string
= '['
.. '"' -- class sub-pattern to match a double quote
.. '%\\' -- class sub-pattern to match a backslash
.. '%z' -- class sub-pattern to match a null
.. '\001' .. '-' .. '\031' -- class sub-pattern to match control characters
.. ']'
local function json_string_literal(value)
local newval = value:gsub(chars_to_be_escaped_in_JSON_string, backslash_replacement_function)
return '"' .. newval .. '"'
end
local function object_or_array(self, T, etc)
--
-- We need to inspect all the keys... if there are any strings, we'll convert to a JSON
-- object. If there are only numbers, it's a JSON array.
--
-- If we'll be converting to a JSON object, we'll want to sort the keys so that the
-- end result is deterministic.
--
local string_keys = { }
local seen_number_key = false
local maximum_number_key
for key in pairs(T) do
if type(key) == 'number' then
seen_number_key = true
if not maximum_number_key or maximum_number_key < key then
maximum_number_key = key
end
elseif type(key) == 'string' then
table.insert(string_keys, key)
else
self:onEncodeError("can't encode table with a key of type " .. type(key), etc)
end
end
if seen_number_key and #string_keys > 0 then
--
-- Mixed key types... don't know what to do, so bail
--
self:onEncodeError("a table with both numeric and string keys could be an object or array; aborting", etc)
elseif #string_keys == 0 then
--
-- An array
--
if seen_number_key then
return nil, maximum_number_key -- an array
else
--
-- An empty table...
--
if tostring(T) == "JSON array" then
return nil
elseif tostring(T) == "JSON object" then
return { }
else
-- have to guess, so we'll pick array, since empty arrays are likely more common than empty objects
return nil
end
end
else
--
-- An object, so return a list of keys
--
table.sort(string_keys)
return string_keys
end
end
--
-- Encode
--
local encode_value -- must predeclare because it calls itself
function encode_value(self, value, parents, etc)
if value == nil then
return 'null'
end
if type(value) == 'string' then
return json_string_literal(value)
elseif type(value) == 'number' then
if value ~= value then
--
-- NaN (Not a Number).
-- JSON has no NaN, so we have to fudge the best we can. This should really be a package option.
--
return "null"
elseif value >= math.huge then
--
-- Positive infinity. JSON has no INF, so we have to fudge the best we can. This should
-- really be a package option. Note: at least with some implementations, positive infinity
-- is both ">= math.huge" and "<= -math.huge", which makes no sense but that's how it is.
-- Negative infinity is properly "<= -math.huge". So, we must be sure to check the ">="
-- case first.
--
return "1e+9999"
elseif value <= -math.huge then
--
-- Negative infinity.
-- JSON has no INF, so we have to fudge the best we can. This should really be a package option.
--
return "-1e+9999"
else
return tostring(value)
end
elseif type(value) == 'boolean' then
return tostring(value)
elseif type(value) ~= 'table' then
self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc)
else
--
-- A table to be converted to either a JSON object or array.
--
local T = value
if parents[T] then
self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
else
parents[T] = true
end
local result_value
local object_keys, maximum_number_key = object_or_array(self, T, etc)
if maximum_number_key then
--
-- An array...
--
local ITEMS = { }
for i = 1, maximum_number_key do
table.insert(ITEMS, encode_value(self, T[i], parents, etc))
end
result_value = "[" .. table.concat(ITEMS, ",") .. "]"
elseif object_keys then
--
-- An object
--
--
-- We'll always sort the keys, so that comparisons can be made on
-- the results, etc. The actual order is not particularly
-- important (e.g. it doesn't matter what character set we sort
-- as); it's only important that it be deterministic... the same
-- every time.
--
local PARTS = { }
for _, key in ipairs(object_keys) do
local encoded_key = encode_value(self, tostring(key), parents, etc)
local encoded_val = encode_value(self, T[key], parents, etc)
table.insert(PARTS, string.format("%s:%s", encoded_key, encoded_val))
end
result_value = "{" .. table.concat(PARTS, ",") .. "}"
else
--
-- An empty array/object... we'll treat it as an array, though it should really be an option
--
result_value = "[]"
end
parents[T] = false
return result_value
end
end
local encode_pretty_value -- must predeclare because it calls itself
function encode_pretty_value(self, value, parents, indent, etc)
if type(value) == 'string' then
return json_string_literal(value)
elseif type(value) == 'number' then
return tostring(value)
elseif type(value) == 'boolean' then
return tostring(value)
elseif type(value) == 'nil' then
return 'null'
elseif type(value) ~= 'table' then
self:onEncodeError("can't convert " .. type(value) .. " to JSON", etc)
else
--
-- A table to be converted to either a JSON object or array.
--
local T = value
if parents[T] then
self:onEncodeError("table " .. tostring(T) .. " is a child of itself", etc)
end
parents[T] = true
local result_value
local object_keys = object_or_array(self, T, etc)
if not object_keys then
--
-- An array...
--
local ITEMS = { }
for i = 1, #T do
table.insert(ITEMS, encode_pretty_value(self, T[i], parents, indent, etc))
end
result_value = "[ " .. table.concat(ITEMS, ", ") .. " ]"
else
--
-- An object -- can keys be numbers?
--
local KEYS = { }
local max_key_length = 0
for _, key in ipairs(object_keys) do
local encoded = encode_pretty_value(self, tostring(key), parents, "", etc)
max_key_length = math.max(max_key_length, #encoded)
table.insert(KEYS, encoded)
end
local key_indent = indent .. " "
local subtable_indent = indent .. string.rep(" ", max_key_length + 2 + 4)
local FORMAT = "%s%" .. tostring(max_key_length) .. "s: %s"
local COMBINED_PARTS = { }
for i, key in ipairs(object_keys) do
local encoded_val = encode_pretty_value(self, T[key], parents, subtable_indent, etc)
table.insert(COMBINED_PARTS, string.format(FORMAT, key_indent, KEYS[i], encoded_val))
end
result_value = "{\n" .. table.concat(COMBINED_PARTS, ",\n") .. "\n" .. indent .. "}"
end
parents[T] = false
return result_value
end
end
function OBJDEF:encode(value, etc)
if type(self) ~= 'table' or self.__index ~= OBJDEF then
OBJDEF:onEncodeError("JSON:encode must be called in method format", etc)
end
local parents = {}
return encode_value(self, value, parents, etc)
end
function OBJDEF:encode_pretty(value, etc)
local parents = {}
local subtable_indent = ""
return encode_pretty_value(self, value, parents, subtable_indent, etc)
end
function OBJDEF.__tostring()
return "JSON encode/decode package"
end
OBJDEF.__index = OBJDEF
function OBJDEF:new(args)
local new = { }
if args then
for key, val in pairs(args) do
new[key] = val
end
end
return setmetatable(new, OBJDEF)
end
return OBJDEF:new()
--
-- Version history:
--
-- 20111207.5 Added support for the 'etc' arguments, for better error reporting.
--
-- 20110731.4 More feedback from David Kolf on how to make the tests for Nan/Infinity system independent.
--
-- 20110730.3 Incorporated feedback from David Kolf at http://lua-users.org/wiki/JsonModules:
--
-- * When encoding lua for JSON, Sparse numeric arrays are now handled by
-- spitting out full arrays, such that
-- JSON:encode({"one", "two", [10] = "ten"})
-- returns
-- ["one","two",null,null,null,null,null,null,null,"ten"]
--
-- In 20100810.2 and earlier, only up to the first non-null value would have been retained.
--
-- * When encoding lua for JSON, numeric value NaN gets spit out as null, and infinity as "1+e9999".
-- Version 20100810.2 and earlier created invalid JSON in both cases.
--
-- * Unicode surrogate pairs are now detected when decoding JSON.
--
-- 20100810.2 added some checking to ensure that an invalid Unicode character couldn't leak in to the UTF-8 encoding
--
-- 20100731.1 initial public release
--

@ -47,6 +47,25 @@ function validDjvuFile(filename)
return true
end
function DjvuDocument:getPageText(pageno)
if self.configurable.text_wrap == 1 then
return self.koptinterface:getPageText(self, pageno)
else
return self._document:getPageText(pageno)
end
end
function DjvuDocument:getOCRWord(pageno, rect)
if self.configurable.text_wrap == 1 then
return self.koptinterface:getOCRWord(self, pageno, rect)
else
--local page = self._document:openPage(pageno)
--local word = page:getOCRWord(rect)
--page:close()
--return word
end
end
function DjvuDocument:getUsedBBox(pageno)
-- djvu does not support usedbbox, so fake it.
local used = {}

@ -184,6 +184,14 @@ function Document:getToc()
return self._document:getToc()
end
function Document:getPageText(pageno)
return nil
end
function Document:getOCRWord(pageno, rect)
return nil
end
function Document:renderPage(pageno, rect, zoom, rotation, gamma, render_mode)
local hash = "renderpg|"..self.file.."|"..pageno.."|"..zoom.."|"..rotation.."|"..gamma.."|"..render_mode
local page_size = self:getPageDimensions(pageno, zoom, rotation)

@ -1,9 +1,23 @@
require "dbg"
require "cache"
require "ui/geometry"
require "ui/device"
require "ui/reader/readerconfig"
KoptInterface = {}
KoptInterface = {
tessocr_data = "data",
ocr_lang = "eng",
ocr_type = 0, -- default, for more accuracy use 3
}
ContextCacheItem = CacheItem:new{}
function ContextCacheItem:onFree()
if self.kctx.free then
DEBUG("free koptcontext", self.kctx)
self.kctx:free()
end
end
function KoptInterface:waitForContext(kc)
-- if koptcontext is being processed in background thread
@ -12,10 +26,13 @@ function KoptInterface:waitForContext(kc)
DEBUG("waiting for background rendering")
util.usleep(100000)
end
return kc
end
-- get reflow context
function KoptInterface:getKOPTContext(doc, pageno, bbox)
--[[
get reflow context
--]]
function KoptInterface:createContext(doc, pageno, bbox)
-- Now koptcontext keeps track of its dst bitmap reflowed by libk2pdfopt.
-- So there is no need to check background context when creating new context.
local kc = KOPTContext.new()
@ -37,6 +54,7 @@ function KoptInterface:getKOPTContext(doc, pageno, bbox)
kc:setLineSpacing(doc.configurable.line_spacing)
kc:setWordSpacing(doc.configurable.word_spacing)
kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1)
if Dbg.is_on then kc:setDebug() end
return kc
end
@ -47,17 +65,6 @@ function KoptInterface:getContextHash(doc, pageno, bbox)
return doc.file.."|"..pageno.."|"..doc.configurable:hash("|").."|"..bbox_hash.."|"..screen_size_hash
end
function KoptInterface:logReflowDuration(pageno, dur)
local file = io.open("reflowlog.txt", "a+")
if file then
if file:seek("end") == 0 then -- write the header only once
file:write("PAGE\tDUR\n")
end
file:write(string.format("%s\t%s\n", pageno, dur))
file:close()
end
end
function KoptInterface:getAutoBBox(doc, pageno)
local bbox = {
x0 = 0, y0 = 0,
@ -68,26 +75,68 @@ function KoptInterface:getAutoBBox(doc, pageno)
local cached = Cache:check(hash)
if not cached then
local page = doc._document:openPage(pageno)
local kc = self:getKOPTContext(doc, pageno, bbox)
local kc = self:createContext(doc, pageno, bbox)
bbox.x0, bbox.y0, bbox.x1, bbox.y1 = page:getAutoBBox(kc)
DEBUG("Auto detected bbox", bbox)
page:close()
Cache:insert(hash, CacheItem:new{ bbox = bbox })
Cache:insert(hash, CacheItem:new{ autobbox = bbox })
return bbox
else
return cached.bbox
return cached.autobbox
end
end
function KoptInterface:getPageText(doc, pageno)
local bbox = doc:getPageBBox(pageno)
local context_hash = self:getContextHash(doc, pageno, bbox)
local hash = "pgtext|"..context_hash
local cached = Cache:check(hash)
if not cached then
local kctx_hash = "kctx|"..context_hash
local cached = Cache:check(kctx_hash)
if cached then
local kc = self:waitForContext(cached.kctx)
local fullwidth, fullheight = kc:getPageDim()
local text = kc:getWordBoxes(0, 0, fullwidth, fullheight)
Cache:insert(hash, CacheItem:new{ pgtext = text })
return text
end
else
return cached.pgtext
end
end
-- get reflowed page dimension from a cached context. wait for background thread
-- if necessary.
function KoptInterface:getReflowedDim(kctx)
self:waitForContext(kctx)
return kctx:getPageDim()
function KoptInterface:getOCRWord(doc, pageno, rect)
local bbox = doc:getPageBBox(pageno)
local context_hash = self:getContextHash(doc, pageno, bbox)
local hash = "ocrword|"..context_hash..rect.x..rect.y..rect.w..rect.h
local cached = Cache:check(hash)
if not cached then
local kctx_hash = "kctx|"..context_hash
local cached = Cache:check(kctx_hash)
if cached then
local kc = self:waitForContext(cached.kctx)
local fullwidth, fullheight = kc:getPageDim()
local ok, word = pcall(
kc.getOCRWord, kc,
self.tessocr_data,
self.ocr_lang,
self.ocr_type,
rect.x, rect.y,
rect.w, rect.h)
Cache:insert(hash, CacheItem:new{ ocrword = word })
return word
end
else
return cached.ocrword
end
end
-- get cached koptcontext for centain page. if context doesn't exist in cache make
-- new context and reflow the src page immediatly.
--[[
get cached koptcontext for centain page. if context doesn't exist in cache make
new context and reflow the src page immediatly, or wait background thread for
reflowed context.
--]]
function KoptInterface:getCachedContext(doc, pageno)
local bbox = doc:getPageBBox(pageno)
local context_hash = self:getContextHash(doc, pageno, bbox)
@ -95,7 +144,7 @@ function KoptInterface:getCachedContext(doc, pageno)
local cached = Cache:check(kctx_hash)
if not cached then
-- If kctx is not cached, create one and get reflowed bmp in foreground.
local kc = self:getKOPTContext(doc, pageno, bbox)
local kc = self:createContext(doc, pageno, bbox)
local page = doc._document:openPage(pageno)
-- reflow page
--local secs, usecs = util.gettime()
@ -107,22 +156,27 @@ function KoptInterface:getCachedContext(doc, pageno)
--self:logReflowDuration(pageno, dur)
local fullwidth, fullheight = kc:getPageDim()
DEBUG("reflowed page", pageno, "fullwidth:", fullwidth, "fullheight:", fullheight)
Cache:insert(kctx_hash, CacheItem:new{ kctx = kc })
Cache:insert(kctx_hash, ContextCacheItem:new{ kctx = kc })
return kc
else
return cached.kctx
-- wait for background thread
return self:waitForContext(cached.kctx)
end
end
-- get reflowed page dimensions
--[[
get reflowed page dimensions
--]]
function KoptInterface:getPageDimensions(doc, pageno, zoom, rotation)
local kctx = self:getCachedContext(doc, pageno)
local fullwidth, fullheight = self:getReflowedDim(kctx)
local kc = self:getCachedContext(doc, pageno)
local fullwidth, fullheight = kc:getPageDim()
return Geom:new{ w = fullwidth, h = fullheight }
end
-- inherited from common document interface
-- render reflowed page into tile cache.
--[[
inherited from common document interface
render reflowed page into tile cache.
--]]
function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode)
doc.render_mode = render_mode
local bbox = doc:getPageBBox(pageno)
@ -132,8 +186,8 @@ function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode
local cached = Cache:check(renderpg_hash)
if not cached then
-- do the real reflowing if kctx is not been cached yet
local kctx = self:getCachedContext(doc, pageno)
local fullwidth, fullheight = self:getReflowedDim(kctx)
local kc = self:getCachedContext(doc, pageno)
local fullwidth, fullheight = kc:getPageDim()
if not Cache:willAccept(fullwidth * fullheight / 2) then
-- whole page won't fit into cache
error("aborting, since we don't have enough cache for this page")
@ -146,10 +200,8 @@ function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode
pageno = pageno,
bb = Blitbuffer.new(fullwidth, fullheight)
}
page:rfdraw(kctx, tile.bb)
page:rfdraw(kc, tile.bb)
page:close()
-- free dst bitmap in kctx as soon as we draw the bitmap to blitbuffer
kctx:free()
Cache:insert(renderpg_hash, tile)
return tile
else
@ -157,29 +209,33 @@ function KoptInterface:renderPage(doc, pageno, rect, zoom, rotation, render_mode
end
end
-- inherited from common document interface
-- render reflowed page into cache in background thread. this method returns immediatly
-- leaving the precache flag on in context. subsequent usage of this context should
-- wait for the precache flag off by calling self:waitForContext(kctx)
--[[
inherited from common document interface
render reflowed page into cache in background thread. this method returns immediatly
leaving the precache flag on in context. subsequent usage of this context should
wait for the precache flag off by calling self:waitForContext(kctx)
--]]
function KoptInterface:hintPage(doc, pageno, zoom, rotation, gamma, render_mode)
local bbox = doc:getPageBBox(pageno)
local context_hash = self:getContextHash(doc, pageno, bbox)
local kctx_hash = "kctx|"..context_hash
local cached = Cache:check(kctx_hash)
if not cached then
local kc = self:getKOPTContext(doc, pageno, bbox)
local kc = self:createContext(doc, pageno, bbox)
local page = doc._document:openPage(pageno)
DEBUG("hinting page", pageno, "in background")
-- reflow will return immediately and running in background thread
kc:setPreCache()
page:reflow(kc, 0)
page:close()
Cache:insert(kctx_hash, CacheItem:new{ kctx = kc })
Cache:insert(kctx_hash, ContextCacheItem:new{ kctx = kc })
end
end
-- inherited from common document interface
-- draw cached tile pixels into target blitbuffer
--[[
inherited from common document interface
draw cached tile pixels into target blitbuffer.
--]]
function KoptInterface:drawPage(doc, target, x, y, rect, pageno, zoom, rotation, render_mode)
local tile = self:renderPage(doc, pageno, rect, zoom, rotation, render_mode)
--DEBUG("now painting", tile, rect)
@ -189,3 +245,17 @@ function KoptInterface:drawPage(doc, target, x, y, rect, pageno, zoom, rotation,
rect.y - tile.excerpt.y,
rect.w, rect.h)
end
--[[
helper functions
--]]
function KoptInterface:logReflowDuration(pageno, dur)
local file = io.open("reflowlog.txt", "a+")
if file then
if file:seek("end") == 0 then -- write the header only once
file:write("PAGE\tDUR\n")
end
file:write(string.format("%s\t%s\n", pageno, dur))
file:close()
end
end

@ -44,6 +44,28 @@ function PdfDocument:unlock(password)
return self:_readMetadata()
end
function PdfDocument:getPageText(pageno)
if self.configurable.text_wrap == 1 then
return self.koptinterface:getPageText(self, pageno)
else
local page = self._document:openPage(pageno)
local text = page:getPageText()
page:close()
return text
end
end
function PdfDocument:getOCRWord(pageno, rect)
if self.configurable.text_wrap == 1 then
return self.koptinterface:getOCRWord(self, pageno, rect)
else
--local page = self._document:openPage(pageno)
--local word = page:getOCRWord(rect)
--page:close()
--return word
end
end
function PdfDocument:getUsedBBox(pageno)
local hash = "pgubbox|"..self.file.."|"..pageno
local cached = Cache:check(hash)

@ -0,0 +1,55 @@
require "ui/device"
require "ui/widget/dict"
ReaderDictionary = EventListener:new{}
function ReaderDictionary:init()
local dev_mod = Device:getModel()
if dev_mod == "KindlePaperWhite" or dev_mod == "KindleTouch" then
require("liblipclua")
JSON = require("JSON")
self.lipc_handle = lipc.init("com.github.koreader.dictionary")
end
end
function ReaderDictionary:onLookupWord(word)
DEBUG("lookup word:", word)
--self:quickLookup()
if self.lipc_handle and JSON and word then
self.lipc_handle:set_string_property(
"com.lab126.booklet.kpvbooklet.dict", "lookup", word)
local results_str = self.lipc_handle:get_string_property(
"com.lab126.booklet.kpvbooklet.word", word)
if results_str then
--DEBUG("def str:", word, definitions)
local ok, results_tab = pcall(JSON.decode, JSON, results_str)
--DEBUG("lookup result table:", word, results_tab)
if results_tab[1] then
self:quickLookup(results_tab[1])
end
end
end
return true
end
function ReaderDictionary:quickLookup(result)
-- UIManager:show(DictQuickLookup:new{
-- dict = "Oxford Dictionary of English",
-- definition = "coordination n. [mass noun] 1 the organization of the different elements of a \
-- complex body or activity so as to enable them to work together effectively: an important managerial \
-- task is the control and coordination of activities. cooperative effort resulting in an effective \
-- relationship: action groups work in coordination with local groups to end rainforest destruction. \
-- the ability to use different parts of the body together smoothly and efficiently: changing from \
-- one foot position to another requires coordination and balance.",
-- id = "/mnt/us/documents/dictionaries/Oxford_Dictionary_of_English.azw",
-- lang = "en",
-- })
if result then
UIManager:show(DictQuickLookup:new{
dict = result.dict,
definition = result.definition,
id = result.ID,
lang = result.lang,
})
end
end

@ -0,0 +1,125 @@
ReaderHighlight = InputContainer:new{}
function ReaderHighlight:init()
if Device:hasKeyboard() then
self.key_events = {
ShowToc = {
{ "." },
doc = _("highlight text") },
}
end
end
function ReaderHighlight:initGesListener()
self.ges_events = {
Tap = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight()
}
}
},
Hold = {
GestureRange:new{
ges = "hold",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight()
}
}
},
}
end
function ReaderHighlight:onSetDimensions(dimen)
-- update listening according to new screen dimen
if Device:isTouchDevice() then
self:initGesListener()
end
end
function ReaderHighlight:onTap(arg, ges)
if self.view.highlight.rect then
self.view.highlight.rect = nil
UIManager:setDirty(self.dialog, "partial")
return true
end
end
function ReaderHighlight:onHold(arg, ges)
self.pos = self.view:screenToPageTransform(ges.pos)
DEBUG("hold position in page", self.pos)
local text = self.ui.document:getPageText(self.pos.page)
--DEBUG("page text", text)
if not text or #text == 0 then
DEBUG("no text extracted")
return true
end
self.word_info = self:getWordFromText(text, self.pos)
DEBUG("hold word info in page", self.word_info)
if self.word_info then
local screen_rect = self.view:pageToScreenTransform(self.pos.page, self.word_info.box)
DEBUG("highlight word rect", screen_rect)
if screen_rect then
self.view.highlight.rect = screen_rect
UIManager:setDirty(self.dialog, "partial")
-- if we extracted text directly
if self.word_info.word then
self.ui:handleEvent(Event:new("LookupWord", self.word_info.word))
-- or we will do OCR
else
UIManager:scheduleIn(0.1, function()
local word_box = self.word_info.box
word_box.x = word_box.x - math.floor(word_box.h * 0.02)
word_box.y = word_box.y - math.floor(word_box.h * 0.02)
word_box.w = word_box.w + math.floor(word_box.h * 0.04)
word_box.h = word_box.h + math.floor(word_box.h * 0.04)
-- local word = self.ui.document:getOCRWord(self.pos.page, word_box)
DEBUG("OCRed word:", word)
self.ui:handleEvent(Event:new("LookupWord", word))
end)
end
end
end
return true
end
function ReaderHighlight:getWordFromText(text, pos)
local function ges_inside(x0, y0, x1, y1)
local x, y = pos.x, pos.y
if x0 ~= nil and y0 ~= nil and x1 ~= nil and y1 ~= nil then
if x0 <= x and y0 <= y and x1 >= x and y1 >= y then
return true
end
end
return false
end
for i = 1, #text do
local l = text[i]
if ges_inside(l.x0, l.y0, l.x1, l.y1) then
--DEBUG("line box", l.x0, l.y0, l.x1, l.y1)
for j = 1, #text[i] do
local w = text[i][j]
if ges_inside(w.x0, w.y0, w.x1, w.y1) then
local box = Geom:new{
x = w.x0, y = w.y0,
w = w.x1 - w.x0,
h = w.y1 - w.y0,
}
return {
word = w.word,
box = box,
}
end -- end if inside word box
end -- end for each word
end -- end if inside line box
end -- end for each line
end

@ -41,3 +41,11 @@ end
function ReaderKoptListener:onFineTuningFontSize(delta)
self.document.configurable.font_size = self.document.configurable.font_size + delta
end
function ReaderKoptListener:onZoomUpdate(zoom)
-- an exceptional case is reflow mode
if self.document.configurable.text_wrap == 1 then
self.view.state.zoom = 1.0
end
end

@ -16,6 +16,10 @@ ReaderView = OverlapGroup:new{
bbox = nil,
},
outer_page_color = 0,
-- hightlight
highlight = {
drawer = "marker" -- show as inverted block instead of underline
},
-- PDF/DjVu continuous paging
page_scroll = nil,
page_bgcolor = 0,
@ -96,6 +100,11 @@ function ReaderView:paintTo(bb, x, y)
self.dim_area.w, self.dim_area.h
)
end
-- draw highlight
if self.highlight.rect then
self:drawHightlight(bb, x, y, self.highlight.rect)
end
-- paint dogear
if self.dogear_visible then
@ -113,6 +122,32 @@ function ReaderView:paintTo(bb, x, y)
self.ui:handleEvent(Event:new("StopActivityIndicator"))
end
--[[
Given coordinates on the screen return position in original page
]]--
function ReaderView:screenToPageTransform(pos)
if self.ui.document.info.has_pages then
if self.page_scroll then
return self:getScrollPagePosition(pos)
else
return self:getSinglePagePosition(pos)
end
end
end
--[[
Given rectangle in original page return rectangle on the screen
]]--
function ReaderView:pageToScreenTransform(page, rect)
if self.ui.document.info.has_pages then
if self.page_scroll then
return self:getScrollPageRect(page, rect)
else
return self:getSinglePageRect(rect)
end
end
end
function ReaderView:drawPageBackground(bb, x, y)
bb:paintRect(x, y, self.dimen.w, self.dimen.h, self.page_bgcolor)
end
@ -155,6 +190,40 @@ function ReaderView:drawScrollPages(bb, x, y)
end)
end
function ReaderView:getScrollPagePosition(pos)
local x_s, y_s = pos.x, pos.y
local x_p, y_p = nil, nil
for _, state in ipairs(self.page_states) do
if y_s < state.visible_area.h + state.offset.y then
y_p = (state.visible_area.y + y_s - state.offset.y) / state.zoom
x_p = (state.visible_area.x + x_s - state.offset.x) / state.zoom
return {
x = x_p,
y = y_p,
page = state.page,
zoom = state.zoom,
rotation = state.rotation,
}
else
y_s = y_s - state.visible_area.h - self.page_gap.height
end
end
end
function ReaderView:getScrollPageRect(page, rect_p)
local rect_s = Geom:new{}
for _, state in ipairs(self.page_states) do
if page == state.page and state.visible_area:contains(rect_p) then
rect_s.x = rect_s.x + state.offset.x + rect_p.x*state.zoom - state.visible_area.x
rect_s.y = rect_s.y + state.offset.y + rect_p.y*state.zoom - state.visible_area.y
rect_s.w = rect_p.w * state.zoom
rect_s.h = rect_p.h * state.zoom
return rect_s
end
rect_s.y = rect_s.y + state.visible_area.h + self.page_gap.height
end
end
function ReaderView:drawPageGap(bb, x, y)
if self.scroll_mode == "vertical" then
bb:paintRect(x, y, self.dimen.w, self.page_gap.height, self.page_gap.color)
@ -179,6 +248,28 @@ function ReaderView:drawSinglePage(bb, x, y)
end)
end
function ReaderView:getSinglePagePosition(pos)
local x_s, y_s = pos.x, pos.y
return {
x = (self.visible_area.x + x_s - self.state.offset.x) / self.state.zoom,
y = (self.visible_area.y + y_s - self.state.offset.y) / self.state.zoom,
page = self.state.page,
zoom = self.state.zoom,
rotation = self.state.rotation,
}
end
function ReaderView:getSinglePageRect(rect_p)
local rect_s = Geom:new{}
if self.visible_area:contains(rect_p) then
rect_s.x = self.state.offset.x + rect_p.x * self.state.zoom - self.visible_area.x
rect_s.y = self.state.offset.y + rect_p.y * self.state.zoom - self.visible_area.y
rect_s.w = rect_p.w * self.state.zoom
rect_s.h = rect_p.h * self.state.zoom
return rect_s
end
end
function ReaderView:drawPageView(bb, x, y)
self.ui.document:drawCurrentViewByPage(
bb,
@ -197,6 +288,26 @@ function ReaderView:drawScrollView(bb, x, y)
self.state.pos)
end
function ReaderView:drawHightlight(bb, x, y, rect)
-- slightly enlarge the highlight box
-- for better viewing experience
local x = rect.x - rect.h * 0.01
local y = rect.y - rect.h * 0.01
local w = rect.w + rect.h * 0.02
local h = rect.h + rect.h * 0.02
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
bb:paintRect(x, y+h-1, w,
self.highlight.line_width,
self.highlight.line_color)
elseif self.highlight.drawer == "marker" then
bb:invertRect(x, y, w, h)
end
end
function ReaderView:getPageArea(page, zoom, rotation)
if self.use_bbox then
return self.ui.document:getUsedBBoxDimensions(page, zoom, rotation)

@ -14,8 +14,10 @@ require "ui/reader/readercropping"
require "ui/reader/readerkopt"
require "ui/reader/readercopt"
require "ui/reader/readerhinting"
require "ui/reader/readerhighlight"
require "ui/reader/readerscreenshot"
require "ui/reader/readerfrontlight"
require "ui/reader/readerdictionary"
require "ui/reader/readerhyphenation"
require "ui/reader/readeractivityindicator"
@ -96,6 +98,22 @@ function ReaderUI:init()
ui = self
}
table.insert(self, reader_bm)
-- text highlight
local highlight = ReaderHighlight:new{
dialog = self.dialog,
view = self[1],
ui = self,
document = self.document,
}
table.insert(self, highlight)
-- dictionary
local dict = ReaderDictionary:new{
dialog = self.dialog,
view = self[1],
ui = self,
document = self.document,
}
table.insert(self, dict)
-- screenshot controller
local reader_ss = ReaderScreenshot:new{
dialog = self.dialog,

@ -0,0 +1,69 @@
require "ui/widget/container"
--[[
Display quick lookup word definition
]]
DictQuickLookup = InputContainer:new{
dict = nil,
definition = nil,
id = nil,
lang = nil,
title_face = Font:getFace("tfont", 20),
content_face = Font:getFace("cfont", 18),
width = Screen:getWidth() - 100
}
function DictQuickLookup:init()
if Device:hasKeyboard() then
key_events = {
AnyKeyPressed = { { Input.group.Any },
seqtext = "any key", doc = _("close dialog") }
}
else
self.ges_events.TapClose = {
GestureRange:new{
ges = "tap",
range = Geom:new{
x = 0, y = 0,
w = Screen:getWidth(),
h = Screen:getHeight(),
}
}
}
end
-- we construct the actual content here because self.text is only available now
self[1] = CenterContainer:new{
dimen = Screen:getSize(),
FrameContainer:new{
margin = 2,
background = 0,
VerticalGroup:new{
align = "center",
-- title bar
TextBoxWidget:new{
text = self.dict,
face = self.title_face,
width = self.width,
},
VerticalSpan:new{ width = 20 },
TextBoxWidget:new{
text = self.definition,
face = self.content_face,
width = self.width,
}
}
}
}
end
function DictQuickLookup:onAnyKeyPressed()
-- triggered by our defined key events
UIManager:close(self)
return true
end
function DictQuickLookup:onTapClose()
UIManager:close(self)
return true
end

@ -1 +1 @@
Subproject commit 8f26cc3264f21006b5079030381bd593deb9c9e0
Subproject commit 3726a3a3aaa590edf6ffc15f4a59e587c9253bce

@ -9,6 +9,9 @@ test -e $PROC_FIVEWAY && echo unlock > $PROC_FIVEWAY
# we're always starting from our working directory
cd /mnt/us/koreader/
# export trained OCR data directory
export TESSDATA_PREFIX="data"
# bind-mount system fonts
if ! grep /mnt/us/koreader/fonts/host /proc/mounts; then
mount -o bind /usr/java/lib/fonts /mnt/us/koreader/fonts/host

Loading…
Cancel
Save