2
0
mirror of https://github.com/koreader/koreader synced 2024-11-11 19:11:14 +00:00
koreader/frontend/fontlist.lua
NiLuJe 5c24470ea9
Logger: Use serpent instead of dump (#9588)
* Persist: support serpent, and use by default over dump (as we assume consistency > readability in Persist).
* Logger/Dbg: Use serpent instead of dump to dump tables (it's slightly more compact, honors __tostring, and will tag tables with their ref, which can come in handy when debugging).
* Dbg: Don't duplicate Logger's log function, just use it directly.
* Fontlist/ConfigDialog: Use serpent for the debug dump.
* Call `os.setlocale(C, "numeric")` on startup instead of peppering it around dump calls. It's process-wide, so it didn't make much sense.
* Trapper: Use LuaJIT's serde facilities instead of dump. They're more reliable in the face of funky input, much faster, and in this case, the data never makes it to human eyes, so a human-readable format didn't gain us anything.
2022-10-06 02:21:03 +02:00

277 lines
8.7 KiB
Lua

local CanvasContext = require("document/canvascontext")
local DataStorage = require("datastorage")
local FT = require("ffi/freetype")
local HB = require("ffi/harfbuzz")
local Persist = require("persist")
local util = require("util")
local lfs = require("libs/libkoreader-lfs")
local logger = require("logger")
local FontList = {
fontdir = "./fonts",
cachedir = DataStorage:getDataDir() .. "/cache/fontlist", -- in a subdirectory, so as not to mess w/ the Cache module.
fontlist = {},
fontinfo = {},
fontnames = {},
}
--[[
These non-LGC Kindle system fonts fail CRe's moronic header check.
Also applies to a number of LGC fonts that have different family names for different styles...
(Those are actually "fixed" via FontConfig in the stock system).
--]]
local kindle_fonts_blacklist = {
["DiwanMuna-Bold.ttf"] = true,
["DiwanMuna-Regular.ttf"] = true,
["HYGothicBold.ttf"] = true,
["HYGothicMedium.ttf"] = true,
["HYMyeongJoBold.ttf"] = true,
["HYMyeongJoMedium.ttf"] = true,
["KindleBlackboxBoldItalic.ttf"] = true,
["KindleBlackboxBold.ttf"] = true,
["KindleBlackboxItalic.ttf"] = true,
["KindleBlackboxRegular.ttf"] = true,
["Kindle_MonospacedSymbol.ttf"] = true,
["Kindle_Symbol.ttf"] = true,
["MTChineseSurrogates.ttf"] = true,
["MYingHeiTBold.ttf"] = true,
["MYingHeiTMedium.ttf"] = true,
["NotoNaskhArabicUI-Bold.ttf"] = true,
["NotoNaskhArabicUI-Regular.ttf"] = true,
["NotoNaskh-Bold.ttf"] = true,
["NotoNaskh-Regular.ttf"] = true,
["NotoSansDevanagari-Regular.ttf"] = true,
["NotoSansBengali-Regular.ttf"] = true,
["NotoSansGujarati-Regular.ttf"] = true,
["NotoSansKannada-Regular.ttf"] = true,
["NotoSansMalayalam-Regular.ttf"] = true,
["NotoSansTamil-Regular.ttf"] = true,
["NotoSansTelugu-Regular.ttf"] = true,
["SakkalKitab-Bold.ttf"] = true,
["SakkalKitab-Regular.ttf"] = true,
["SongTBold.ttf"] = true,
["SongTMedium.ttf"] = true,
["STHeitiBold.ttf"] = true,
["STHeitiMedium.ttf"] = true,
["STSongBold.ttf"] = true,
["STSongMedium.ttf"] = true,
["TBGothicBold_213.ttf"] = true,
["TBGothicMed_213.ttf"] = true,
["TBMinchoBold_213.ttf"] = true,
["TBMinchoMedium_213.ttf"] = true,
["STKaiMedium.ttf"] = true,
["Amazon-Ember-Bold.ttf"] = false,
["Amazon-Ember-BoldItalic.ttf"] = false,
["Amazon-Ember-Heavy.ttf"] = true,
["Amazon-Ember-HeavyItalic.ttf"] = true,
["Amazon-Ember-Medium.ttf"] = true,
["Amazon-Ember-MediumItalic.ttf"] = true,
["Amazon-Ember-Regular.ttf"] = false,
["Amazon-Ember-RegularItalic.ttf"] = false,
["AmazonEmberBold-Bold.ttf"] = true,
["AmazonEmberBold-BoldItalic.ttf"] = true,
["AmazonEmberBold-Italic.ttf"] = true,
["AmazonEmberBold-Regular.ttf"] = true,
["Caecilia_LT_65_Medium.ttf"] = false,
["Caecilia_LT_66_Medium_Italic.ttf"] = true,
["Caecilia_LT_75_Bold.ttf"] = false,
["Caecilia_LT_76_Bold_Italic.ttf"] = true,
["Caecilia_LT_67_Cond_Medium.ttf"] = true,
["Caecilia_LT_68_Cond_Medium_Italic.ttf"] = true,
["Caecilia_LT_77_Cond_Bold.ttf"] = true,
["Caecilia_LT_78_Cond_Bold_Italic.ttf"] = true,
["Futura-Bold.ttf"] = true,
["Futura-BoldOblique.ttf"] = true,
["Helvetica_LT_65_Medium.ttf"] = false,
["Helvetica_LT_66_Medium_Italic.ttf"] = true,
["Helvetica_LT_75_Bold.ttf"] = true,
["Helvetica_LT_76_Bold_Italic.ttf"] = true,
}
local function isInFontsBlacklist(f)
-- write test for this
return CanvasContext:isKindle() and kindle_fonts_blacklist[f]
end
local function getExternalFontDir()
if CanvasContext:hasSystemFonts() then
return require("frontend/ui/elements/font_settings"):getPath()
else
return os.getenv("EXT_FONT_DIR")
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
-- If family_name is missing, it's probably too broken to be useful
if face.family_name ~= nil then
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()
end
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[1] then return self.fontlist end
local cache = Persist:new{
path = self.cachedir .. "/fontinfo.dat",
codec = "zstd",
}
local t, err = cache:load()
if not t then
logger.info(cache.path, err, "-> initializing it")
-- Create new subdirectory
lfs.mkdir(self.cachedir)
end
self.fontinfo = t or {}
-- 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
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
-- Update the on-disk cache if necessary
if mark.cache_dirty then
cache:save(self.fontinfo)
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("FontList registered:", v.name)
end
names[v.name] = nlist
table.insert(nlist, v)
end
end
table.sort(self.fontlist)
return self.fontlist
end
function FontList:dumpFontList()
local serpent = require("ffi/serpent")
-- FontInfo
local path = self.cachedir .. "/fontinfo_dump.lua"
local f = io.open(path, "w")
if f ~= nil then
f:write(serpent.block(self.fontinfo, { indent = " ", comment = false, nocode = true }))
f:close()
else
return
end
-- FontList
path = self.cachedir .. "/fontlist_dump.lua"
f = io.open(path, "w")
if f ~= nil then
f:write(serpent.block(self.fontlist, { indent = " ", comment = false, nocode = true }))
f:close()
else
return
end
local InfoMessage = require("ui/widget/infomessage")
local UIManager = require("ui/uimanager")
local _ = require("gettext")
local T = require("ffi/util").template
UIManager:show(InfoMessage:new{
text = T(_("Fontlist data has been dumped in:\n%1"), self.cachedir)
})
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
function FontList:getFontArgFunc()
local cre = require("document/credocument"):engineInit()
local toggle = {}
local face_list = cre.getFontFaces()
for _, v in ipairs(face_list) do
table.insert(toggle, FontList:getLocalizedFontName(cre.getFontFaceFilenameAndFaceIndex(v)) or v)
end
return face_list, toggle
end
return FontList