mirror of
https://github.com/koreader/koreader
synced 2024-10-31 21:20:20 +00:00
346d8eb83e
Instead of opening the same font multiple times for each different size (multiple face instances), share one face instance and create multiple size instances.
277 lines
8.7 KiB
Lua
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, ftsize = pcall(FT.newFaceSize, path, nil, i)
|
|
if not ok then
|
|
return nil
|
|
end
|
|
|
|
-- If family_name is missing, it's probably too broken to be useful
|
|
if ftsize.face.family_name ~= nil then
|
|
local fres = ftsize:getInfo()
|
|
local hbface = HB.hb_ft_face_create_referenced(ftsize.face)
|
|
fres.names = hbface:getNames()
|
|
fres.scripts, fres.langs = hbface:getCoverage()
|
|
fres.path = path
|
|
fres.index = i
|
|
table.insert(res, fres)
|
|
hbface:destroy()
|
|
end
|
|
ftsize: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
|