|
|
@ -1,8 +1,17 @@
|
|
|
|
local CanvasContext = require("document/canvascontext")
|
|
|
|
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 = {
|
|
|
|
local FontList = {
|
|
|
|
fontdir = "./fonts",
|
|
|
|
fontdir = "./fonts",
|
|
|
|
fontlist = {},
|
|
|
|
fontlist = {},
|
|
|
|
|
|
|
|
fontinfo = {},
|
|
|
|
|
|
|
|
fontnames = {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
--[[
|
|
|
|
--[[
|
|
|
@ -95,37 +104,134 @@ local function getExternalFontDir()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local function _readList(target, dir)
|
|
|
|
-- Query FreeType/HarfBuzz about font metadata
|
|
|
|
-- lfs.dir non-existent directory will give an error, weird!
|
|
|
|
local function collectFaceInfo(path)
|
|
|
|
local ok, iter, dir_obj = pcall(lfs.dir, dir)
|
|
|
|
local res = {}
|
|
|
|
if not ok then return end
|
|
|
|
local n = FT.getFaceCount(path)
|
|
|
|
for f in iter, dir_obj do
|
|
|
|
if not n then
|
|
|
|
local mode = lfs.attributes(dir.."/"..f, "mode")
|
|
|
|
return
|
|
|
|
if mode == "directory" and f ~= "." and f ~= ".." then
|
|
|
|
end
|
|
|
|
_readList(target, dir.."/"..f)
|
|
|
|
for i=0, n-1 do
|
|
|
|
elseif mode == "file" or mode == "link" then
|
|
|
|
local ok, face = pcall(FT.newFace, path, nil, i)
|
|
|
|
if string.sub(f, 1, 1) ~= "." then
|
|
|
|
if not ok then
|
|
|
|
local file_type = string.lower(string.match(f, ".+%.([^.]+)") or "")
|
|
|
|
return nil
|
|
|
|
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
|
|
|
|
|
|
|
|
end
|
|
|
|
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
|
|
|
|
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
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
function FontList:getFontList()
|
|
|
|
function FontList:getFontList()
|
|
|
|
if #self.fontlist > 0 then return self.fontlist end
|
|
|
|
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
|
|
|
|
-- multiple paths should be joined with semicolon
|
|
|
|
for dir in string.gmatch(getExternalFontDir() or "", "([^;]+)") do
|
|
|
|
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
|
|
|
|
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)
|
|
|
|
table.sort(self.fontlist)
|
|
|
|
return self.fontlist
|
|
|
|
return self.fontlist
|
|
|
|
end
|
|
|
|
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
|
|
|
|
return FontList
|
|
|
|