You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

236 lines
7.6 KiB

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 logger = require("logger")
local dbg = require("dbg")
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,
["NotoSansBengali-Regular.ttf"] = true,
["NotoSansDevanagari-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 > 0 then return self.fontlist end
local cache = Persist:new{
path = self.cachedir .. "/fontinfo.dat"
}
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
if dbg.is_verbose then
-- when verbose debug is on, always dump the cache in plain text (to inspect the db output)
cache:save(self.fontinfo)
elseif mark.cache_dirty then
-- otherwise dump the db in binary (more compact), and only if something has changed
cache:save(self.fontinfo, true)
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