fontlist: Cache categorized font info (for mupdf) (#6786)

Info about each face (l10n, name, family, style etc) is
now cached offline, so fonts can be queried ahead of time.

Fixes https://github.com/koreader/koreader/issues/6763
reviewable/pr6794/r1
ezdiy 4 years ago committed by GitHub
parent 9fc87e3b85
commit a73bf1afda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,6 +4,7 @@ local ConfirmBox = require("ui/widget/confirmbox")
local Device = require("device")
local Event = require("ui/event")
local Font = require("ui/font")
local FontList = require("fontlist")
local Input = Device.input
local InputContainer = require("ui/widget/container/inputcontainer")
local Menu = require("ui/widget/menu")
@ -61,12 +62,17 @@ function ReaderFont:init()
-- Font list
local face_list = cre.getFontFaces()
for k,v in ipairs(face_list) do
local font_filename, font_faceindex = cre.getFontFaceFilenameAndFaceIndex(v)
table.insert(self.face_table, {
text_func = function()
-- defaults are hardcoded in credocument.lua
local default_font = G_reader_settings:readSetting("cre_font") or self.ui.document.default_font
local fallback_font = G_reader_settings:readSetting("fallback_font") or self.ui.document.fallback_fonts[1]
local text = v
if font_filename and font_faceindex then
text = FontList:getLocalizedFontName(font_filename, font_faceindex) or text
end
if v == default_font then
text = text .. ""
end
@ -77,7 +83,6 @@ function ReaderFont:init()
end,
font_func = function(size)
if G_reader_settings:nilOrTrue("font_menu_use_font_face") then
local font_filename, font_faceindex = cre.getFontFaceFilenameAndFaceIndex(v)
if font_filename and font_faceindex then
return Font:getFace(font_filename, size, font_faceindex)
end

@ -1,8 +1,17 @@
local CanvasContext = require("document/canvascontext")
local DataStorage = require("datastorage")
local dump = require("dump")
local FT = require("ffi/freetype")
local HB = require("ffi/harfbuzz")
local util = require("util")
local logger = require("logger")
local dbg = require("dbg")
local FontList = {
fontdir = "./fonts",
fontlist = {},
fontinfo = {},
fontnames = {},
}
--[[
@ -95,37 +104,134 @@ local function getExternalFontDir()
end
end
local function _readList(target, dir)
-- lfs.dir non-existent directory will give an error, weird!
local ok, iter, dir_obj = pcall(lfs.dir, dir)
if not ok then return end
for f in iter, dir_obj do
local mode = lfs.attributes(dir.."/"..f, "mode")
if mode == "directory" and f ~= "." and f ~= ".." then
_readList(target, dir.."/"..f)
elseif mode == "file" or mode == "link" then
if string.sub(f, 1, 1) ~= "." then
local file_type = string.lower(string.match(f, ".+%.([^.]+)") or "")
if file_type == "ttf" or file_type == "ttc"
or file_type == "cff" or file_type == "otf" then
if not isInFontsBlacklist(f) then
table.insert(target, dir.."/"..f)
end
end
end
-- Query FreeType/HarfBuzz about font metadata
local function collectFaceInfo(path)
local res = {}
local n = FT.getFaceCount(path)
if not n then
return
end
for i=0, n-1 do
local ok, face = pcall(FT.newFace, path, nil, i)
if not ok then
return nil
end
local fres = face:getInfo()
local hbface = HB.hb_ft_face_create_referenced(face)
fres.names = hbface:getNames()
fres.scripts, fres.langs = hbface:getCoverage()
fres.path = path
fres.index = i
table.insert(res, fres)
hbface:destroy()
face:done()
end
return res
end
local font_exts = {
["ttf"] = true,
["ttc"] = true,
["cff"] = true,
["otf"] = true,
}
function FontList:_readList(dir, mark)
util.findFiles(dir, function(path, file, attr)
-- See if we're interested
if file:sub(1,1) == "." then return end
local file_type = file:lower():match(".+%.([^.]+)") or ""
if not font_exts[file_type] then return end
-- Add it to the list
if not isInFontsBlacklist(file) then
table.insert(self.fontlist, path)
end
-- And into cached info table
mark[path] = true
if self.fontinfo[path] and (self.fontinfo[path].change == attr.change) then
return
end
local fi = collectFaceInfo(path)
if not fi then return end
fi.change = attr.change
self.fontinfo[path] = fi
mark.cache_dirty = true
end)
end
function FontList:getFontList()
if #self.fontlist > 0 then return self.fontlist end
_readList(self.fontlist, self.fontdir)
local cache_file = DataStorage:getDataDir() .. "/cache/fontinfo.dat"
local ok
ok, self.fontinfo = pcall(dofile, cache_file)
if not ok or not self.fontinfo then
self.fontinfo = {}
end
-- used for marking fonts we're seeing
local mark = { cache_dirty = false }
self:_readList(self.fontdir, mark)
-- multiple paths should be joined with semicolon
for dir in string.gmatch(getExternalFontDir() or "", "([^;]+)") do
_readList(self.fontlist, dir)
self:_readList(dir, mark)
end
-- clear fonts that no longer exist
for k, _ in pairs(self.fontinfo) do
if not mark[k] then
self.fontinfo[k] = nil
mark.cache_dirty = true
end
end
if dbg.is_verbose then
-- when verbose debug is on, always dump the cache in plain text (to inspect the db output)
local cache = io.open(cache_file, "w")
cache:write("return " .. dump(self.fontinfo))
cache:close()
elseif mark.cache_dirty then
-- otherwise dump the db in binary (more compact), and only if something has changed
local bc = load("return " .. dump(self.fontinfo))
bc = string.dump(bc, true)
local cache = io.open(cache_file, "wb")
cache:write(bc)
cache:close()
end
local names = self.fontnames
for _,coll in pairs(self.fontinfo) do
for _,v in ipairs(coll) do
local nlist = names[v.name] or {}
assert(v.name)
if #nlist == 0 then
logger.dbg("FONTNAMES ADD: ", v.name)
end
names[v.name] = nlist
table.insert(nlist, v)
end
end
table.sort(self.fontlist)
return self.fontlist
end
-- Try to determine the localized font name
function FontList:getLocalizedFontName(file, index)
local lang = G_reader_settings:readSetting("language")
if not lang then return end
lang = lang:lower():gsub("_","-")
local altname = self.fontinfo[file]
altname = altname and altname[index+1]
altname = altname and altname.names and (altname.names[lang] or altname.names[lang:match("%w+")])
altname = altname and (altname[tonumber(HB.HB_OT_NAME_ID_FULL_NAME)] or altname[tonumber(HB.HB_OT_NAME_ID_FONT_FAMILY)])
if not altname then return end -- ensure nil
return altname
end
return FontList

@ -580,6 +580,28 @@ function util.getFilesystemType(path)
return type
end
--- Recursively scan directory for files inside
-- @string path
-- @function callback(fullpath, name, attr)
function util.findFiles(dir, cb)
local function scan(current)
local ok, iter, dir_obj = pcall(lfs.dir, current)
if not ok then return end
for f in iter, dir_obj do
local path = current.."/"..f
local attr = lfs.attributes(path)
if attr.mode == "directory" then
if f ~= "." and f ~= ".." then
scan(path)
end
else
cb(path, f, attr)
end
end
end
scan(dir)
end
--- Checks if directory is empty.
---- @string path
---- @treturn bool

Loading…
Cancel
Save