<commit_before>local TileCacheItem = require("document/tilecacheitem")
local KOPTContext = require("ffi/koptcontext")
local Document = require("document/document")
local DataStorage = require("datastorage")
local CacheItem = require("cacheitem")
local Screen = require("device").screen
local Geom = require("ui/geometry")
local serial = require("serialize")
local Cache = require("cache")
local DEBUG = require("dbg")
local logger = require("logger")
local util = require("ffi/util")

local KoptInterface = {
    ocrengine = "ocrengine",
    tessocr_data = DataStorage:getDataDir() .. "/data",
    ocr_lang = "eng",
    ocr_type = 3, -- default 0, for more accuracy use 3
    last_context_size = nil,
    default_context_size = 1024*1024,
    screen_dpi = Screen:getDPI(),
}

local ContextCacheItem = CacheItem:new{}

function ContextCacheItem:onFree()
    if self.kctx.free then
        KoptInterface:waitForContext(self.kctx)
        logger.dbg("free koptcontext", self.kctx)
        self.kctx:free()
    end
end

function ContextCacheItem:dump(filename)
    if self.kctx:isPreCache() == 0 then
        logger.dbg("dumping koptcontext to", filename)
        return serial.dump(self.size, KOPTContext.totable(self.kctx), filename)
    end
end

function ContextCacheItem:load(filename)
    logger.dbg("loading koptcontext from", filename)
    local size, kc_table = serial.load(filename)
    self.size = size
    self.kctx = KOPTContext.fromtable(kc_table)
end

local OCREngine = CacheItem:new{}

function OCREngine:onFree()
    if self.ocrengine.freeOCR then
        logger.dbg("free OCREngine", self.ocrengine)
        self.ocrengine:freeOCR()
    end
end

function KoptInterface:waitForContext(kc)
    -- if koptcontext is being processed in background thread
    -- the isPreCache will return 1.
    while kc and kc:isPreCache() == 1 do
        logger.dbg("waiting for background rendering")
        util.usleep(100000)
    end
    return kc
end

--[[
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()
    local screen_size = Screen:getSize()
    local lang = doc.configurable.doc_language
    if lang == "chi_sim" or lang == "chi_tra" or
        lang == "jpn" or lang == "kor" then
        kc:setCJKChar()
    end
    kc:setLanguage(lang)
    kc:setTrim(doc.configurable.trim_page)
    kc:setWrap(doc.configurable.text_wrap)
    kc:setIndent(doc.configurable.detect_indent)
    kc:setColumns(doc.configurable.max_columns)
    kc:setDeviceDim(screen_size.w, screen_size.h)
    kc:setDeviceDPI(self.screen_dpi)
    kc:setStraighten(doc.configurable.auto_straighten)
    kc:setJustification(doc.configurable.justification)
    kc:setWritingDirection(doc.configurable.writing_direction)
    kc:setZoom(doc.configurable.font_size)
    kc:setMargin(doc.configurable.page_margin)
    kc:setQuality(doc.configurable.quality)
    kc:setContrast(doc.configurable.contrast)
    kc:setDefectSize(doc.configurable.defect_size)
    kc:setLineSpacing(doc.configurable.line_spacing)
    kc:setWordSpacing(doc.configurable.word_spacing)
    if bbox then
        if bbox.x0 >= bbox.x1 or bbox.y0 >= bbox.y1 then
            local page_size = Document.getNativePageDimensions(doc, pageno)
            bbox.x0, bbox.y0 = 0, 0
            bbox.x1, bbox.y1 = page_size.w, page_size.h
        end
        kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1)
    end
    if DEBUG.is_on then kc:setDebug() end
    return kc
end

function KoptInterface:getContextHash(doc, pageno, bbox)
    local screen_size = Screen:getSize()
    local screen_size_hash = screen_size.w.."|"..screen_size.h
    local bbox_hash = bbox.x0.."|"..bbox.y0.."|"..bbox.x1.."|"..bbox.y1
    return doc.file.."|"..doc.mod_time.."|"..pageno.."|"
            ..doc.configurable:hash("|").."|"..bbox_hash.."|"..screen_size_hash
end

function KoptInterface:getPageBBox(doc, pageno)
    if doc.configurable.text_wrap ~= 1 and doc.configurable.trim_page == 1 then
        -- auto bbox finding
        return self:getAutoBBox(doc, pageno)
    elseif doc.configurable.text_wrap ~= 1 and doc.configurable.trim_page == 2 then
        -- semi-auto bbox finding
        return self:getSemiAutoBBox(doc, pageno)
    else
        -- get saved manual bbox
        return Document.getPageBBox(doc, pageno)
    end
end

--[[
auto detect bbox
--]]
function KoptInterface:getAutoBBox(doc, pageno)
    local native_size = Document.getNativePageDimensions(doc, pageno)
    local bbox = {
        x0 = 0, y0 = 0,
        x1 = native_size.w,
        y1 = native_size.h,
    }
    local context_hash = self:getContextHash(doc, pageno, bbox)
    local hash = "autobbox|"..context_hash
    local cached = Cache:check(hash)
    if not cached then
        local page = doc._document:openPage(pageno)
        local kc = self:createContext(doc, pageno, bbox)
        page:getPagePix(kc)
        local x0, y0, x1, y1 = kc:getAutoBBox()
        local w, h = native_size.w, native_size.h
        if (x1 - x0)/w > 0.1 or (y1 - y0)/h > 0.1 then
            bbox.x0, bbox.y0, bbox.x1, bbox.y1 = x0, y0, x1, y1
        else
            bbox = Document.getPageBBox(doc, pageno)
        end
        Cache:insert(hash, CacheItem:new{ autobbox = bbox })
        page:close()
        kc:free()
        return bbox
    else
        return cached.autobbox
    end
end

--[[
detect bbox within user restricted bbox
--]]
function KoptInterface:getSemiAutoBBox(doc, pageno)
    -- use manual bbox
    local bbox = Document.getPageBBox(doc, pageno)
    local context_hash = self:getContextHash(doc, pageno, bbox)
    local hash = "semiautobbox|"..context_hash
    local cached = Cache:check(hash)
    if not cached then
        local page = doc._document:openPage(pageno)
        local kc = self:createContext(doc, pageno, bbox)
        local auto_bbox = {}
        page:getPagePix(kc)
        auto_bbox.x0, auto_bbox.y0, auto_bbox.x1, auto_bbox.y1 = kc:getAutoBBox()
        auto_bbox.x0 = auto_bbox.x0 + bbox.x0
        auto_bbox.y0 = auto_bbox.y0 + bbox.y0
        auto_bbox.x1 = auto_bbox.x1 + bbox.x0
        auto_bbox.y1 = auto_bbox.y1 + bbox.y0
        logger.dbg("Semi-auto detected bbox", auto_bbox)
        page:close()
        Cache:insert(hash, CacheItem:new{ semiautobbox = auto_bbox })
        kc:free()
        return auto_bbox
    else
        return cached.semiautobbox
    end
end

--[[
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.<commit_msg>Fix crash with kopt semi-auto crop on blank page<commit_after>local TileCacheItem = require("document/tilecacheitem")
local KOPTContext = require("ffi/koptcontext")
local Document = require("document/document")
local DataStorage = require("datastorage")
local CacheItem = require("cacheitem")
local Screen = require("device").screen
local Geom = require("ui/geometry")
local serial = require("serialize")
local Cache = require("cache")
local DEBUG = require("dbg")
local logger = require("logger")
local util = require("ffi/util")

local KoptInterface = {
    ocrengine = "ocrengine",
    tessocr_data = DataStorage:getDataDir() .. "/data",
    ocr_lang = "eng",
    ocr_type = 3, -- default 0, for more accuracy use 3
    last_context_size = nil,
    default_context_size = 1024*1024,
    screen_dpi = Screen:getDPI(),
}

local ContextCacheItem = CacheItem:new{}

function ContextCacheItem:onFree()
    if self.kctx.free then
        KoptInterface:waitForContext(self.kctx)
        logger.dbg("free koptcontext", self.kctx)
        self.kctx:free()
    end
end

function ContextCacheItem:dump(filename)
    if self.kctx:isPreCache() == 0 then
        logger.dbg("dumping koptcontext to", filename)
        return serial.dump(self.size, KOPTContext.totable(self.kctx), filename)
    end
end

function ContextCacheItem:load(filename)
    logger.dbg("loading koptcontext from", filename)
    local size, kc_table = serial.load(filename)
    self.size = size
    self.kctx = KOPTContext.fromtable(kc_table)
end

local OCREngine = CacheItem:new{}

function OCREngine:onFree()
    if self.ocrengine.freeOCR then
        logger.dbg("free OCREngine", self.ocrengine)
        self.ocrengine:freeOCR()
    end
end

function KoptInterface:waitForContext(kc)
    -- if koptcontext is being processed in background thread
    -- the isPreCache will return 1.
    while kc and kc:isPreCache() == 1 do
        logger.dbg("waiting for background rendering")
        util.usleep(100000)
    end
    return kc
end

--[[
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()
    local screen_size = Screen:getSize()
    local lang = doc.configurable.doc_language
    if lang == "chi_sim" or lang == "chi_tra" or
        lang == "jpn" or lang == "kor" then
        kc:setCJKChar()
    end
    kc:setLanguage(lang)
    kc:setTrim(doc.configurable.trim_page)
    kc:setWrap(doc.configurable.text_wrap)
    kc:setIndent(doc.configurable.detect_indent)
    kc:setColumns(doc.configurable.max_columns)
    kc:setDeviceDim(screen_size.w, screen_size.h)
    kc:setDeviceDPI(self.screen_dpi)
    kc:setStraighten(doc.configurable.auto_straighten)
    kc:setJustification(doc.configurable.justification)
    kc:setWritingDirection(doc.configurable.writing_direction)
    kc:setZoom(doc.configurable.font_size)
    kc:setMargin(doc.configurable.page_margin)
    kc:setQuality(doc.configurable.quality)
    kc:setContrast(doc.configurable.contrast)
    kc:setDefectSize(doc.configurable.defect_size)
    kc:setLineSpacing(doc.configurable.line_spacing)
    kc:setWordSpacing(doc.configurable.word_spacing)
    if bbox then
        if bbox.x0 >= bbox.x1 or bbox.y0 >= bbox.y1 then
            local page_size = Document.getNativePageDimensions(doc, pageno)
            bbox.x0, bbox.y0 = 0, 0
            bbox.x1, bbox.y1 = page_size.w, page_size.h
        end
        kc:setBBox(bbox.x0, bbox.y0, bbox.x1, bbox.y1)
    end
    if DEBUG.is_on then kc:setDebug() end
    return kc
end

function KoptInterface:getContextHash(doc, pageno, bbox)
    local screen_size = Screen:getSize()
    local screen_size_hash = screen_size.w.."|"..screen_size.h
    local bbox_hash = bbox.x0.."|"..bbox.y0.."|"..bbox.x1.."|"..bbox.y1
    return doc.file.."|"..doc.mod_time.."|"..pageno.."|"
            ..doc.configurable:hash("|").."|"..bbox_hash.."|"..screen_size_hash
end

function KoptInterface:getPageBBox(doc, pageno)
    if doc.configurable.text_wrap ~= 1 and doc.configurable.trim_page == 1 then
        -- auto bbox finding
        return self:getAutoBBox(doc, pageno)
    elseif doc.configurable.text_wrap ~= 1 and doc.configurable.trim_page == 2 then
        -- semi-auto bbox finding
        return self:getSemiAutoBBox(doc, pageno)
    else
        -- get saved manual bbox
        return Document.getPageBBox(doc, pageno)
    end
end

--[[
auto detect bbox
--]]
function KoptInterface:getAutoBBox(doc, pageno)
    local native_size = Document.getNativePageDimensions(doc, pageno)
    local bbox = {
        x0 = 0, y0 = 0,
        x1 = native_size.w,
        y1 = native_size.h,
    }
    local context_hash = self:getContextHash(doc, pageno, bbox)
    local hash = "autobbox|"..context_hash
    local cached = Cache:check(hash)
    if not cached then
        local page = doc._document:openPage(pageno)
        local kc = self:createContext(doc, pageno, bbox)
        page:getPagePix(kc)
        local x0, y0, x1, y1 = kc:getAutoBBox()
        local w, h = native_size.w, native_size.h
        if (x1 - x0)/w > 0.1 or (y1 - y0)/h > 0.1 then
            bbox.x0, bbox.y0, bbox.x1, bbox.y1 = x0, y0, x1, y1
        else
            bbox = Document.getPageBBox(doc, pageno)
        end
        Cache:insert(hash, CacheItem:new{ autobbox = bbox })
        page:close()
        kc:free()
        return bbox
    else
        return cached.autobbox
    end
end

--[[
detect bbox within user restricted bbox
--]]
function KoptInterface:getSemiAutoBBox(doc, pageno)
    -- use manual bbox
    local bbox = Document.getPageBBox(doc, pageno)
    local context_hash = self:getContextHash(doc, pageno, bbox)
    local hash = "semiautobbox|"..context_hash
    local cached = Cache:check(hash)
    if not cached then
        local page = doc._document:openPage(pageno)
        local kc = self:createContext(doc, pageno, bbox)
        local auto_bbox = {}
        page:getPagePix(kc)
        auto_bbox.x0, auto_bbox.y0, auto_bbox.x1, auto_bbox.y1 = kc:getAutoBBox()
        auto_bbox.x0 = auto_bbox.x0 + bbox.x0
        auto_bbox.y0 = auto_bbox.y0 + bbox.y0
        auto_bbox.x1 = auto_bbox.x1 + bbox.x0
        auto_bbox.y1 = auto_bbox.y1 + bbox.y0
        logger.dbg("Semi-auto detected bbox", auto_bbox)
        local native_size = Document.getNativePageDimensions(doc, pageno)
        if (auto_bbox.x1 - auto_bbox.x0)/native_size.w < 0.1 and (auto_bbox.y1 - auto_bbox.y0)/native_size.h < 0.1 then
            logger.dbg("Semi-auto detected bbox too small, using manual bbox")
            auto_bbox = bbox
        end
        page:close()
        Cache:insert(hash, CacheItem:new{ semiautobbox = auto_bbox })
        kc:free()
        return auto_bbox
    else
        return cached.semiautobbox
    end
end

--[[
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.
{'fraction_non_alphanumeric': 0.11016764641846288, 'fraction_numerical': 0.01291820886856811, 'mean_word_length': 3.104855525767054, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6796929', 'n_tokens_mistral': 4880, 'n_tokens_neox': 4735, 'n_words': 1049}