summaryrefslogtreecommitdiff
path: root/luaotfload-database.lua
diff options
context:
space:
mode:
Diffstat (limited to 'luaotfload-database.lua')
-rw-r--r--luaotfload-database.lua2314
1 files changed, 1584 insertions, 730 deletions
diff --git a/luaotfload-database.lua b/luaotfload-database.lua
index 34c06d1..1deca07 100644
--- a/luaotfload-database.lua
+++ b/luaotfload-database.lua
@@ -20,100 +20,105 @@ local C, Cc, Cf, Cg, Cs, Ct
= lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Cs, lpeg.Ct
--- Luatex builtins
-local load = load
-local next = next
-local pcall = pcall
-local require = require
-local tonumber = tonumber
-local unpack = table.unpack
-
-local fontloaderinfo = fontloader.info
-local fontloaderclose = fontloader.close
-local fontloaderopen = fontloader.open
-local fontloaderto_table = fontloader.to_table
-local iolines = io.lines
-local ioopen = io.open
-local kpseexpand_path = kpse.expand_path
-local kpseexpand_var = kpse.expand_var
-local kpsefind_file = kpse.find_file
-local kpselookup = kpse.lookup
-local kpsereadable_file = kpse.readable_file
-local lfsattributes = lfs.attributes
-local lfschdir = lfs.chdir
-local lfscurrentdir = lfs.currentdir
-local lfsdir = lfs.dir
-local mathabs = math.abs
-local mathmin = math.min
-local osremove = os.remove
-local stringfind = string.find
-local stringformat = string.format
-local stringgmatch = string.gmatch
-local stringgsub = string.gsub
-local stringlower = string.lower
-local stringsub = string.sub
-local stringupper = string.upper
-local tableconcat = table.concat
-local tablesort = table.sort
-local texiowrite_nl = texio.write_nl
-local utf8gsub = unicode.utf8.gsub
-local utf8lower = unicode.utf8.lower
+local load = load
+local next = next
+local pcall = pcall
+local require = require
+local tonumber = tonumber
+local unpack = table.unpack
+
+local fontloaderinfo = fontloader.info
+local fontloaderclose = fontloader.close
+local fontloaderopen = fontloader.open
+local fontloaderto_table = fontloader.to_table
+local gzipload = gzip.load
+local gzipsave = gzip.save
+local iolines = io.lines
+local ioopen = io.open
+local kpseexpand_path = kpse.expand_path
+local kpseexpand_var = kpse.expand_var
+local kpsefind_file = kpse.find_file
+local kpselookup = kpse.lookup
+local kpsereadable_file = kpse.readable_file
+local lfsattributes = lfs.attributes
+local lfschdir = lfs.chdir
+local lfscurrentdir = lfs.currentdir
+local lfsdir = lfs.dir
+local mathabs = math.abs
+local mathmin = math.min
+local osgettimeofday = os.gettimeofday
+local osremove = os.remove
+local stringfind = string.find
+local stringformat = string.format
+local stringgmatch = string.gmatch
+local stringgsub = string.gsub
+local stringlower = string.lower
+local stringsub = string.sub
+local stringupper = string.upper
+local tableconcat = table.concat
+local tablesort = table.sort
+local texiowrite_nl = texio.write_nl
+local utf8gsub = unicode.utf8.gsub
+local utf8lower = unicode.utf8.lower
--- these come from Lualibs/Context
-local getwritablepath = caches.getwritablepath
-local filebasename = file.basename
-local filecollapsepath = file.collapsepath or file.collapse_path
-local filedirname = file.dirname
-local fileextname = file.extname
-local fileiswritable = file.iswritable
-local filejoin = file.join
-local filenameonly = file.nameonly
-local filereplacesuffix = file.replacesuffix
-local filesplitpath = file.splitpath or file.split_path
-local filesuffix = file.suffix
-local lfsisdir = lfs.isdir
-local lfsisfile = lfs.isfile
-local lfsmkdirs = lfs.mkdirs
-local stringis_empty = string.is_empty
-local stringsplit = string.split
-local stringstrip = string.strip
-local tableappend = table.append
-local tablecopy = table.copy
-local tablefastcopy = table.fastcopy
-local tabletofile = table.tofile
-local tabletohash = table.tohash
-
-local runasscript = caches == nil
-
+local filebasename = file.basename
+local filecollapsepath = file.collapsepath or file.collapse_path
+local filedirname = file.dirname
+local fileextname = file.extname
+local fileiswritable = file.iswritable
+local filejoin = file.join
+local filenameonly = file.nameonly
+local filereplacesuffix = file.replacesuffix
+local filesplitpath = file.splitpath or file.split_path
+local filesuffix = file.suffix
+local getwritablepath = caches.getwritablepath
+local lfsisdir = lfs.isdir
+local lfsisfile = lfs.isfile
+local lfsmkdirs = lfs.mkdirs
+local lpegsplitat = lpeg.splitat
+local stringis_empty = string.is_empty
+local stringsplit = string.split
+local stringstrip = string.strip
+local tableappend = table.append
+local tablecopy = table.copy
+local tablefastcopy = table.fastcopy
+local tabletofile = table.tofile
+local tabletohash = table.tohash
+local tableserialize = table.serialize
+local runasscript = caches == nil
--- the font loader namespace is “fonts”, same as in Context
--- we need to put some fallbacks into place for when running
--- as a script
fonts = fonts or { }
fonts.names = fonts.names or { }
fonts.definers = fonts.definers or { }
-local names = fonts.names --- font index namespace
-config = config or { }
-config.luaotfload = config.luaotfload or { }
-config.luaotfload.resolver = config.luaotfload.resolver or "normal"
-config.luaotfload.formats = config.luaotfload.formats or "otf,ttf,ttc,dfont"
+local names = fonts.names
+local name_index = nil -- upvalue for names.data
+
+local luaotfloadconfig = config.luaotfload --- always present
+luaotfloadconfig.resolver = luaotfloadconfig.resolver or "normal"
+luaotfloadconfig.formats = luaotfloadconfig.formats or "otf,ttf,ttc,dfont"
+luaotfloadconfig.strip = luaotfloadconfig.strip == true
-if config.luaotfload.update_live ~= false then
+if luaotfloadconfig.update_live ~= false then
--- this option allows for disabling updates
--- during a TeX run
- config.luaotfload.update_live = true
+ luaotfloadconfig.update_live = true
end
-names.version = 2.210
-names.data = nil --- contains the loaded database
-names.lookups = nil --- contains the lookup cache
-
-names.path = { index = { }, lookups = { } }
-names.path.globals = {
- prefix = "", --- writable_path/names_dir
- names_dir = config.luaotfload.names_dir or "names",
- index_file = config.luaotfload.index_file
- or "luaotfload-names.lua",
- lookups_file = "luaotfload-lookup-cache.lua",
+names.version = 2.4
+names.data = nil --- contains the loaded database
+names.lookups = nil --- contains the lookup cache
+
+names.path = { index = { }, lookups = { } }
+names.path.globals = {
+ prefix = "", --- writable_path/names_dir
+ names_dir = luaotfloadconfig.names_dir or "names",
+ index_file = luaotfloadconfig.index_file
+ or "luaotfload-names.lua",
+ lookups_file = "luaotfload-lookup-cache.lua",
}
--- string -> (string * string)
@@ -139,6 +144,16 @@ local noncomma = 1-comma
local splitcomma = Ct((C(noncomma^1) + comma)^1)
patterns.splitcomma = splitcomma
+local format_precedence = {
+ "otf", "ttc", "ttf",
+ "dfont", "afm", "pfb",
+ "pfa",
+}
+
+local location_precedence = {
+ "local", "system", "texmf",
+}
+
--[[doc--
We use the functions in the cache.* namespace that come with the
fontloader (see luat-basics-gen). it’s safe to use for the most part
@@ -183,14 +198,42 @@ end
Auxiliary functions
--doc]]--
+--- fontnames contain all kinds of garbage; as a precaution we
+--- lowercase and strip them of non alphanumerical characters
+
--- string -> string
-local sanitize_string = function (str)
+
+local invalidchars = "[^%a%d]"
+
+local sanitize_fontname = function (str)
if str ~= nil then
- return utf8gsub(utf8lower(str), "[^%a%d]", "")
+ str = utf8gsub (utf8lower (str), invalidchars, "")
+ return str
end
return nil
end
+local sanitize_fontnames = function (rawnames)
+ local result = { }
+ for category, namedata in next, rawnames do
+
+ if type (namedata) == "string" then
+ result [category] = utf8gsub (utf8lower (namedata),
+ invalidchars,
+ "")
+ else
+ local target = { }
+ for field, name in next, namedata do
+ target [field] = utf8gsub (utf8lower (name),
+ invalidchars,
+ "")
+ end
+ result [category] = target
+ end
+ end
+ return result
+end
+
local find_files_indeed
find_files_indeed = function (acc, dirs, filter)
if not next (dirs) then --- done
@@ -242,17 +285,44 @@ end
This is a sketch of the luaotfload db:
type dbobj = {
- formats : string list; // { "otf", "ttf", "ttc", "dfont" }
- mappings : fontentry list;
- status : filestatus;
- version : float;
- // new in v2.3; these supersede the basenames / barenames
- // hashes from v2.2
- filenames : filemap;
+ families : familytable;
+ files : filemap;
+ status : filestatus;
+ mappings : fontentry list;
+ meta : metadata;
+ names : namedata; // TODO: check for relevance after db is finalized
+ }
+ and familytable = {
+ local : (format, familyentry) hash; // specified with include dir
+ texmf : (format, familyentry) hash;
+ system : (format, familyentry) hash;
+ }
+ and familyentry = {
+ regular : sizes;
+ italic : sizes;
+ bold : sizes;
+ bolditalic : sizes;
+ }
+ and sizes = {
+ default : int; // points into mappings or names
+ optical : (int, int) list; // design size -> index entry
+ }
+ and metadata = {
+ formats : string list; // { "otf", "ttf", "ttc", "dfont" }
+ statistics : TODO;
+ version : float;
}
and filemap = {
- base : (string, int) hash; // basename -> idx
- bare : (string, int) hash; // barename -> idx
+ base : {
+ local : (string, int) hash; // basename -> idx
+ system : (string, int) hash;
+ texmf : (string, int) hash;
+ };
+ bare : {
+ local : (string, (string, int) hash) hash; // location -> (barename -> idx)
+ system : (string, (string, int) hash) hash;
+ texmf : (string, (string, int) hash) hash;
+ };
full : (int, string) hash; // idx -> full path
}
and fontentry = {
@@ -275,13 +345,14 @@ This is a sketch of the luaotfload db:
size : int list;
slant : int;
subfont : int;
- texmf : bool;
+ location : local | system | texmf;
weight : int;
width : int;
- units_per_em : int; // mainly 1000, but also 2048 or 256
+ units_per_em : int; // mainly 1000, but also 2048 or 256
}
- and filestatus = (fullname,
- { index : int list; timestamp : int }) dict
+ and filestatus = (string, // fullname
+ { index : int list; // pointer into mappings
+ timestamp : int; }) dict
beware that this is a reconstruction and may be incomplete.
@@ -320,13 +391,18 @@ mtx-fonts has in names.tma:
--doc]]--
-local fontnames_init = function (formats) --- returns dbobj
+local initialize_namedata = function (formats) --- returns dbobj
return {
- mappings = { },
- status = { },
--- filenames = { }, -- created later
- version = names.version,
- formats = formats,
+ --families = { },
+ status = { }, -- was: status; map abspath -> mapping
+ mappings = { }, -- TODO: check if still necessary after rewrite
+ names = { },
+-- files = { }, -- created later
+ meta = {
+ formats = formats,
+ statistics = { },
+ version = names.version,
+ },
}
end
@@ -336,27 +412,37 @@ end
--- string -> (string * table)
local load_lua_file = function (path)
- local foundname = filereplacesuffix(path, "luc")
+ local foundname = filereplacesuffix (path, "luc")
- local fh = ioopen(foundname, "rb") -- try bin first
+ if false then
+ local fh = ioopen (foundname, "rb") -- try bin first
if fh then
local chunk = fh:read"*all"
fh:close()
- code = load(chunk, "b")
+ code = load (chunk, "b")
end
+end
if not code then --- fall back to text file
- foundname = filereplacesuffix(path, "lua")
+ foundname = filereplacesuffix (path, "lua")
fh = ioopen(foundname, "rb")
if fh then
local chunk = fh:read"*all"
fh:close()
- code = load(chunk, "t")
+ code = load (chunk, "t")
+ end
+ end
+
+ if not code then --- probe gzipped file
+ foundname = filereplacesuffix (path, "lua.gz")
+ local chunk = gzipload (foundname)
+ if chunk then
+ code = load (chunk, "t")
end
end
if not code then return nil, nil end
- return foundname, code()
+ return foundname, code ()
end
--- define locals in scope
@@ -371,7 +457,7 @@ local load_lookups
local read_blacklist
local read_fonts_conf
local reload_db
-local resolve
+local resolve_name
local resolve_cached
local resolve_fullpath
local save_names
@@ -381,7 +467,6 @@ local get_font_filter
local set_font_filter
--- state of the database
-local fonts_loaded = false
local fonts_reloaded = false
--- limit output when approximate font matching (luaotfload-tool -F)
@@ -389,16 +474,16 @@ local fuzzy_limit = 1 --- display closest only
--- bool? -> dbobj
load_names = function (dry_run)
- local starttime = os.gettimeofday ()
+ local starttime = osgettimeofday ()
local foundname, data = load_lua_file (names.path.index.lua)
if data then
- report ("both", 1, "db",
+ report ("both", 2, "db",
"Font names database loaded", "%s", foundname)
report ("info", 3, "db", "Loading took %0.f ms",
- 1000*(os.gettimeofday()-starttime))
+ 1000 * (osgettimeofday () - starttime))
- local db_version, nms_version = data.version, names.version
+ local db_version, nms_version = data.meta.version, names.version
if db_version ~= nms_version then
report ("both", 0, "db",
[[Version mismatch; expected %4.3f, got %4.3f]],
@@ -417,13 +502,12 @@ load_names = function (dry_run)
[[Font names database not found, generating new one.]])
report ("both", 0, "db",
[[This can take several minutes; please be patient.]])
- data = update_names (fontnames_init (get_font_filter ()),
+ data = update_names (initialize_namedata (get_font_filter ()),
nil, dry_run)
if not data then
report ("both", 0, "db", "Database creation unsuccessful.")
end
end
- fonts_loaded = true
return data
end
@@ -441,57 +525,65 @@ load_lookups = function ( )
return data
end
-local style_synonyms = { set = { } }
-do
- local combine = function (ta, tb)
- local result = { }
- for i=1, #ta do
- for j=1, #tb do
- result[#result+1] = ta[i] .. tb[j]
- end
- end
- return result
- end
-
- --- read this: http://blogs.adobe.com/typblography/2008/05/indesign_font_conflicts.html
- --- tl;dr: font style synonyms are unreliable.
- ---
- --- Context matches font names against lists of known identifiers
- --- for weight, style, width, and variant, so that including
- --- the family name there are five dimensions for choosing a
- --- match. The sad thing is, while this is a decent heuristic it
- --- makes no sense to imitate it in luaotfload because the user
- --- interface must fit into the much more limited Xetex scheme that
- --- distinguishes between merely four style categories (variants):
- --- “regular”, “italic”, “bold”, and “bolditalic”. As a result,
- --- some of the styles are lumped together although they can differ
- --- significantly (like “medium” and “bold”).
-
- --- Xetex (XeTeXFontMgr.cpp) appears to recognize only “plain”,
- --- “normal”, and “roman” as synonyms for “regular”.
- local list = {
- regular = { "normal", "roman",
- "plain", "book",
- "light", "extralight",
- "ultralight", },
- bold = { "demi", "demibold",
- "semibold", "boldregular",
- "medium", "mediumbold",
- "ultrabold", "extrabold",
- "heavy", "black",
- "bold", },
- italic = { "regularitalic", "normalitalic",
- "oblique", "slanted",
- "italic", },
- }
-
- list.bolditalic = combine(list.bold, list.italic)
- style_synonyms.list = list
-
- for category, synonyms in next, style_synonyms.list do
- style_synonyms.set[category] = tabletohash(synonyms, true)
- end
-end
+--local style_synonyms = { set = { } }
+--do
+-- local combine = function (ta, tb)
+-- local result = { }
+-- for i=1, #ta do
+-- for j=1, #tb do
+-- result[#result+1] = ta[i] .. tb[j]
+-- end
+-- end
+-- return result
+-- end
+--
+-- --- read this: http://blogs.adobe.com/typblography/2008/05/indesign_font_conflicts.html
+-- --- tl;dr: font style synonyms are unreliable.
+-- ---
+-- --- Context matches font names against lists of known identifiers
+-- --- for weight, style, width, and variant, so that including
+-- --- the family name there are five dimensions for choosing a
+-- --- match. The sad thing is, while this is a decent heuristic it
+-- --- makes no sense to imitate it in luaotfload because the user
+-- --- interface must fit into the much more limited Xetex scheme that
+-- --- distinguishes between merely four style categories (variants):
+-- --- “regular”, “italic”, “bold”, and “bolditalic”. As a result,
+-- --- some of the styles are lumped together although they can differ
+-- --- significantly (like “medium” and “bold”).
+--
+-- --- Xetex (XeTeXFontMgr.cpp) appears to recognize only “plain”,
+-- --- “normal”, and “roman” as synonyms for “regular”.
+-- local list = {
+-- regular = { "normal", "roman",
+-- "plain", "book",
+-- "light", "extralight",
+-- "ultralight", },
+-- bold = { "demi", "demibold",
+-- "semibold", "boldregular",
+-- "medium", "mediumbold",
+-- "ultrabold", "extrabold",
+-- "heavy", "black",
+-- "bold", },
+-- italic = { "regularitalic", "normalitalic",
+-- "oblique", "slanted",
+-- "italic", },
+-- }
+--
+-- list.bolditalic = combine(list.bold, list.italic)
+-- style_synonyms.list = list
+--
+-- for category, synonyms in next, style_synonyms.list do
+-- style_synonyms.set[category] = tabletohash(synonyms, true)
+-- end
+--end
+
+local regular_synonym = {
+ book = true,
+ normal = true,
+ plain = true,
+ regular = true,
+ roman = true,
+}
local type1_formats = { "tfm", "ofm", }
@@ -519,18 +611,17 @@ end
--- string -> (string * string * bool)
crude_file_lookup_verbose = function (filename)
- if not names.data then names.data = load_names() end
- local data = names.data
- local mappings = data.mappings
- local filenames = data.filenames
+ if not name_index then name_index = load_names() end
+ local mappings = name_index.mappings
+ local files = name_index.files
local found
--- look up in db first ...
- found = verbose_lookup(filenames, "bare", filename)
+ found = verbose_lookup(files, "bare", filename)
if found then
return found, nil, true
end
- found = verbose_lookup(filenames, "base", filename)
+ found = verbose_lookup(files, "base", filename)
if found then
return found, nil, true
end
@@ -545,24 +636,51 @@ crude_file_lookup_verbose = function (filename)
return filename, nil, false
end
+local lookup_filename = function (filename)
+ if not name_index then name_index = load_names () end
+ local files = name_index.files
+ local basedata = files.base
+ local baredata = files.bare
+ for i = 1, #location_precedence do
+ local location = location_precedence [i]
+ local basenames = basedata [location]
+ local barenames = baredata [location]
+ local idx
+ if basenames ~= nil then
+ idx = basenames [filename]
+ if idx then
+ goto done
+ end
+ end
+ if barenames ~= nil then
+ for j = 1, #format_precedence do
+ local format = format_precedence [j]
+ local filemap = barenames [format]
+ if filemap then
+ idx = barenames [format] [filename]
+ if idx then
+ break
+ end
+ end
+ end
+ end
+::done::
+ if idx then
+ return files.full [idx]
+ end
+ end
+end
+
--- string -> (string * string * bool)
crude_file_lookup = function (filename)
- if not names.data then names.data = load_names() end
- local data = names.data
- local mappings = data.mappings
- local filenames = data.filenames
-
- local found
+ local found = lookup_filename (filename)
- found = filenames.base[filename]
- or filenames.bare[filename]
+ if not found then
+ found = dummy_findfile(filename)
+ end
if found then
- found = filenames.full[found]
- if found == nil then
- found = dummy_findfile(filename)
- end
- return found or filename, nil, true
+ return found, nil, true
end
for i=1, #type1_formats do
@@ -580,15 +698,19 @@ Existence of the resolved file name is verified differently depending
on whether the index entry has a texmf flag set.
--doc]]--
-local get_font_file = function (fullnames, entry)
+local get_font_file = function (index)
+ local entry = name_index.mappings [index]
+ if not entry then
+ return false
+ end
local basename = entry.basename
- if entry.texmf == true then
+ if entry.location == "texmf" then
if kpselookup(basename) then
return true, basename, entry.subfont
end
- else
- local fullname = fullnames[entry.index]
- if lfsisfile(fullname) then
+ else --- system, local
+ local fullname = name_index.files.full [index]
+ if lfsisfile (fullname) then
return true, basename, entry.subfont
end
end
@@ -601,15 +723,15 @@ the texmf or filesystem.
--doc]]--
local verify_font_file = function (basename)
- if not names.data then names.data = load_names() end
- local filenames = names.data.filenames
- local idx = filenames.base[basename]
+ if not name_index then name_index = load_names() end
+ local files = name_index.files
+ local idx = files.base[basename]
if not idx then
return false
end
--- firstly, check filesystem
- local fullname = filenames.full[idx]
+ local fullname = files.full[idx]
if fullname and lfsisfile(fullname) then
return true
end
@@ -713,6 +835,10 @@ resolve_cached = function (_, _, specification)
local entry = { filename, subfont }
report("both", 4, "cache", "New entry: %s", request)
names.lookups[request] = entry
+
+ --- obviously, the updated cache needs to be stored.
+ --- TODO this should trigger a save only once the
+ --- document is compiled (finish_pdffile callback?)
report("both", 5, "cache", "Saving updated cache")
local success = save_lookups()
if not success then --- sad, but not critical
@@ -752,285 +878,242 @@ local add_to_match = function (found, size, face)
return found, continue
end
+local choose_closest = function (distances)
+ local closest = 2^51
+ local match
+ for i = 1, #distances do
+ local d, index = unpack (distances [i])
+ if d < closest then
+ closest = d
+ match = index
+ end
+ end
+ return match
+end
+
--[[doc--
-Luatex-fonts, the font-loader package luaotfload imports, comes with
-basic file location facilities (see luatex-fonts-syn.lua).
-However, not only does the builtin functionality rely on Context’s font
-name database, it is also too limited to be of more than basic use.
-For this reason, luaotfload supplies its own resolvers that accesses
-the font database created by the luaotfload-tool script.
+ choose_size -- Pick a font face of appropriate size from the list
+ of family members with matching style. There are three categories:
---doc]]--
+ 1. exact matches: if there is a face whose design size equals
+ the asked size, it is returned immediately and no further
+ candidates are inspected.
+ 2. range matches: of all faces in whose design range the
+ requested size falls the one whose center the requested
+ size is closest to is returned.
----
---- the request specification has the fields:
----
---- · features: table
---- · normal: set of { ccmp clig itlc kern liga locl mark mkmk rlig }
---- · ???
---- · forced: string
---- · lookup: "name"
---- · method: string
---- · name: string
---- · resolved: string
---- · size: int
---- · specification: string (== <lookup> ":" <name>)
---- · sub: string
----
---- The “size” field deserves special attention: if its value is
---- negative, then it actually specifies a scalefactor of the
---- design size of the requested font. This happens e.g. if a font is
---- requested without an explicit “at size”. If the font is part of a
---- larger collection with different design sizes, this complicates
---- matters a bit: Normally, the resolver prefers fonts that have a
---- design size as close as possible to the requested size. If no
---- size specified, then the design size is implied. But which design
---- size should that be? Xetex appears to pick the “normal” (unmarked)
---- size: with Adobe fonts this would be the one that is neither
---- “caption” nor “subhead” nor “display” &c ... For fonts by Adobe this
---- seems to be the one that does not receive a “prefmodifiers” field.
---- (IOW Adobe uses the “prefmodifiers” field to encode the design size
---- in more or less human readable format.) However, this is not true
---- of LM and EB Garamond. As this matters only where there are
---- multiple design sizes to a given font/style combination, we put a
---- workaround in place that chooses that unmarked version.
-
----
---- the first return value of “resolve” is the file name of the
---- requested font (string)
---- the second is of type bool or string and indicates the subfont of a
---- ttc
----
---- 'a -> 'a -> table -> (string * string | bool * bool)
----
-
-resolve = function (_, _, specification) -- the 1st two parameters are used by ConTeXt
- if not fonts_loaded then names.data = load_names() end
- local data = names.data
-
- local name = sanitize_string(specification.name)
- local style = sanitize_string(specification.style) or "regular"
-
- local askedsize
-
- if specification.optsize then
- askedsize = tonumber(specification.optsize)
- else
- local specsize = specification.size
- if specsize and specsize >= 0 then
- askedsize = specsize / 65536
- end
- end
+ 3. out-of-range matches: of all other faces (i. e. whose range
+ is above or below the asked size) the one is chosen whose
+ boundary (upper or lower) is closest to the requested size.
- if type(data) ~= "table" then
- --- this catches a case where load_names() doesn’t
- --- return a database object, which can happen only
- --- in case there is valid Lua code in the database,
- --- but it’s not a table, e.g. it contains an integer.
- if not fonts_reloaded then
- return reload_db("invalid database; not a table",
- resolve, nil, nil, specification)
- end
- --- unsucessfully reloaded; bail
- return specification.name, false, false
- end
+ 4. default matches: if no design size or a design size of zero
+ is requested, the face with the default size is returned.
- if not data.mappings then
- if not fonts_reloaded then
- return reload_db("invalid database; missing font mapping",
- resolve, nil, nil, specification)
- end
- return specification.name, false, false
- end
-
- local synonym_set = style_synonyms.set
- local stylesynonyms = synonym_set[style]
- local regularsynonyms = synonym_set.regular
-
- local exact = { } --> collect exact style matches
- local synonymous = { } --> collect matching style synonyms
- local fallback --> e.g. non-matching style (fontspec is anal about this)
- local candidates = { } --> secondary results, incomplete matches
-
- for n, face in next, data.mappings do
- local family, metafamily
- local prefmodifiers, fontstyle_name, subfamily
- local psname, fullname, fontname, pfullname
-
- local facenames = face.sanitized
- if facenames then
- family = facenames.family
- subfamily = facenames.subfamily
- fontstyle_name = facenames.fontstyle_name
- prefmodifiers = facenames.prefmodifiers or fontstyle_name or subfamily
- fullname = facenames.fullname
- psname = facenames.psname
- fontname = facenames.fontname
- pfullname = facenames.pfullname
- metafamily = facenames.metafamily
- end
- fontname = fontname or sanitize_string(face.fontname)
- pfullname = pfullname or sanitize_string(face.fullname)
-
- if name == family
- or name == metafamily
- then
- if style == prefmodifiers
- or style == fontstyle_name
- then
- local continue
- exact, continue = add_to_match(exact, askedsize, face)
- if continue == false then break end
- elseif style == subfamily then
- exact = add_to_match(exact, askedsize, face)
- elseif stylesynonyms and stylesynonyms[prefmodifiers]
- or regularsynonyms[prefmodifiers]
- then
- --- treat synonyms for prefmodifiers as first-class
- --- (needed to prioritize DejaVu Book over Condensed)
- exact = add_to_match(exact, askedsize, face)
- elseif name == fullname
- or name == pfullname
- or name == fontname
- or name == psname
- then
- synonymous = add_to_match(synonymous, askedsize, face)
- elseif stylesynonyms and stylesynonyms[subfamily]
- or regularsynonyms[subfamily]
- then
- synonymous = add_to_match(synonymous, askedsize, face)
- elseif prefmodifiers == "regular"
- or subfamily == "regular" then
- fallback = face
- else --- mark as last straw but continue
- candidates[#candidates+1] = face
- end
- else
- if name == fullname
- or name == pfullname
- or name == fontname
- or name == psname then
- local continue
- exact, continue = add_to_match(exact, askedsize, face)
- if continue == false then break end
+--doc]]--
+
+--- int * int * int * int list -> int -> int
+local choose_size = function (sizes, askedsize)
+ local mappings = name_index.mappings
+ local match = sizes.default
+ local exact
+ local inrange = { } --- distance * index list
+ local norange = { } --- distance * index list
+ local fontname, subfont
+ if askedsize ~= 0 then
+ --- firstly, look for an exactly matching design size or
+ --- matching range
+ for i = 1, #sizes do
+ local dsnsize, high, low, index = unpack (sizes [i])
+ if dsnsize == askedsize then
+ --- exact match, this is what we were looking for
+ exact = index
+ goto skip
+ elseif askedsize < low then
+ --- below range, add to the norange table
+ local d = low - askedsize
+ norange [#norange + 1] = { d, index }
+ elseif askedsize > high then
+ --- beyond range, add to the norange table
+ local d = askedsize - high
+ norange [#norange + 1] = { d, index }
+ else
+ --- range match
+ local d = ((low + high) / 2) - askedsize
+ if d < 0 then
+ d = -d
+ end
+ inrange [#inrange + 1] = { d, index }
end
end
end
-
- local found
- if next(exact) then
- found = exact
- else
- found = synonymous
+::skip::
+ if exact then
+ match = exact
+ elseif #inrange > 0 then
+ match = choose_closest (inrange)
+ elseif #norange > 0 then
+ match = choose_closest (norange)
end
+ return match
+end
- --- this is a monster
- if #found == 1 then
- --- “found” is really synonymous with “registered in the db”.
- local entry = found[1]
- local success, filename, subfont
- = get_font_file(data.filenames.full, entry)
- if success == true then
- report("log", 0, "resolve",
- "Font family='%s', subfamily='%s' found: %s",
- name, style, filename
- )
- return filename, subfont, true
- end
-
- elseif #found > 1 then
- -- we found matching font(s) but not in the requested optical
- -- sizes, so we loop through the matches to find the one with
- -- least difference from the requested size.
- local match
-
- if askedsize then --- choose by design size
- local closest
- local least = math.huge -- initial value is infinity
+--[[doc--
- for i, face in next, found do
- local dsnsize = face.size and face.size [1] or 0
- local difference = mathabs (dsnsize - askedsize)
- if difference < least then
- closest = face
- least = difference
- end
- end
+ resolve_familyname -- Query the families table for an entry
+ matching the specification.
+ The parameters “name” and “style” are pre-sanitized.
- match = closest
- else --- choose “unmarked” match, for Adobe fonts this
- --- is the one without a “prefmodifiers” field.
- match = found [1] --- fallback
- for i, face in next, found do
- if not face.sanitized.prefmodifiers then
- match = face
- break
+--doc]]--
+--- spec -> string -> string -> int -> string * int
+local resolve_familyname = function (specification, name, style, askedsize)
+ local families = name_index.families
+ local mappings = name_index.mappings
+ local candidates = nil
+ --- arrow code alert
+ for i = 1, #location_precedence do
+ local location = location_precedence [i]
+ local locgroup = families [location]
+ for j = 1, #format_precedence do
+ local format = format_precedence [j]
+ local fmtgroup = locgroup [format]
+ if fmtgroup then
+ local familygroup = fmtgroup [name]
+ if familygroup then
+ local stylegroup = familygroup [style]
+ if stylegroup then --- suitable match
+ candidates = stylegroup
+ goto done
+ end
end
end
end
+ end
+ if true then
+ return nil, nil
+ end
+::done::
+ index = choose_size (candidates, askedsize)
+ local success, resolved, subfont = get_font_file (index)
+ if not success then
+ return nil, nil
+ end
+ report ("info", 2, "db", "Match found: %s(%d)",
+ resolved, subfont or 0)
+ return resolved, subfont
+end
- local success, filename, subfont
- = get_font_file(data.filenames.full, match)
- if success == true then
- report("log", 0, "resolve",
- "Font family='%s', subfamily='%s' found: %s",
- name, style, filename
- )
- return filename, subfont, true
+local resolve_fontname = function (specification, name)
+ local mappings = name_index.mappings
+ for i = 1, #mappings do
+ local face = mappings [i]
+ if face.fontname == name
+ or face.fullname == name
+ or face.psname == name
+ then
+ return face.basename, face.subfont
end
+ end
+ return nil, nil
+end
- elseif fallback then
- local success, filename, subfont
- = get_font_file(data.filenames.full, fallback)
- if success == true then
- report("log", 0, "resolve",
- "No exact match for request %s; using fallback",
- specification.specification
- )
- report("log", 0, "resolve",
- "Font family='%s', subfamily='%s' found: %s",
- name, style, filename
- )
- return filename, subfont, true
- end
- elseif next(candidates) then
- --- pick the first candidate encountered
- local entry = candidates[1]
- local success, filename, subfont
- = get_font_file(data.filenames.full, entry)
- if success == true then
- report("log", 0, "resolve",
- "Font family='%s', subfamily='%s' found: %s",
- name, style, filename
- )
- return filename, subfont, true
+--[[doc--
+
+ resolve_name -- Perform a name: lookup. This first queries the
+ font families table and, if there is no match for the spec, the
+ font names table.
+ The return value is a pair consisting of the file name and the
+ subfont index if appropriate..
+
+ the request specification has the fields:
+
+ · features: table
+ · normal: set of { ccmp clig itlc kern liga locl mark mkmk rlig }
+ · ???
+ · forced: string
+ · lookup: "name"
+ · method: string
+ · name: string
+ · resolved: string
+ · size: int
+ · specification: string (== <lookup> ":" <name>)
+ · sub: string
+
+ The “size” field deserves special attention: if its value is
+ negative, then it actually specifies a scalefactor of the
+ design size of the requested font. This happens e.g. if a font is
+ requested without an explicit “at size”. If the font is part of a
+ larger collection with different design sizes, this complicates
+ matters a bit: Normally, the resolver prefers fonts that have a
+ design size as close as possible to the requested size. If no
+ size specified, then the design size is implied. But which design
+ size should that be? Xetex appears to pick the “normal” (unmarked)
+ size: with Adobe fonts this would be the one that is neither
+ “caption” nor “subhead” nor “display” &c ... For fonts by Adobe this
+ seems to be the one that does not receive a “prefmodifiers” field.
+ (IOW Adobe uses the “prefmodifiers” field to encode the design size
+ in more or less human readable format.) However, this is not true
+ of LM and EB Garamond. As this matters only where there are
+ multiple design sizes to a given font/style combination, we put a
+ workaround in place that chooses that unmarked version.
+
+ The first return value of “resolve_name” is the file name of the
+ requested font (string). It can be passed to the fullname resolver
+ get_font_file().
+ The second value is either “false” or an integer indicating the
+ subfont index in a TTC.
+
+--doc]]--
+
+--- table -> string * (int | bool)
+resolve_name = function (specification)
+ local resolved, subfont
+ if not name_index then name_index = load_names () end
+ local name = sanitize_fontname (specification.name)
+ local style = sanitize_fontname (specification.style) or "r"
+ local askedsize = specification.optsize
+
+ if askedsize then
+ askedsize = tonumber (askedsize)
+ else
+ askedsize = specification.size
+ if askedsize and askedsize >= 0 then
+ askedsize = askedsize / 65536
+ else
+ askedsize = 0
end
end
- --- no font found so far
- if not fonts_reloaded then
- --- last straw: try reloading the database
- return reload_db(
- "unresolved font name: '" .. name .. "'",
- resolve, nil, nil, specification
- )
+ resolved, subfont = resolve_familyname (specification, name, style, askedsize)
+ if not resolved then
+ resolved, subfont = resolve_fontname (specification, name)
end
-
- --- else, default to requested name
- return specification.name, false, false
-end --- resolve()
+ if not resolved then
+ resolved = specification.name, false
+ end
+ return resolved, subfont
+end
resolve_fullpath = function (fontname, ext) --- getfilename()
- if not fonts_loaded then
- names.data = load_names()
- end
- local filenames = names.data.filenames
- local idx = filenames.base[fontname]
- or filenames.bare[fontname]
- if idx then
- return filenames.full[idx]
+ if not name_index then name_index = load_names () end
+ local files = name_index.files
+ local basedata = files.base
+ local baredata = files.bare
+ for i = 1, #location_precedence do
+ local location = location_precedence [i]
+ local basenames = basedata [location]
+ local barenames = baredata [location] [ext]
+ local idx
+ if basenames ~= nil then
+ idx = basenames [fontname]
+ end
+ if not idx and barenames ~= nil then
+ idx = barenames [fontname]
+ end
+ if idx then
+ return files.full [idx]
+ end
end
return ""
end
@@ -1040,18 +1123,19 @@ end
--- string -> ('a -> 'a) -> 'a list -> 'a
reload_db = function (why, caller, ...)
- local namedata = names.data
- local formats = tableconcat (namedata.formats, ",")
+ local namedata = name_index
+ local formats = tableconcat (namedata.meta.formats, ",")
report ("both", 1, "db",
"Reload initiated (formats: %s); reason: %q",
formats, why)
set_font_filter (formats)
- names.data = update_names (names.data, false, false)
+ namedata = update_names (namedata, false, false)
- if names.data then
+ if namedata then
fonts_reloaded = true
+ name_index = namedata
return caller (...)
end
@@ -1088,28 +1172,25 @@ end
--- string -> int -> bool
find_closest = function (name, limit)
- local name = sanitize_string(name)
+ local name = sanitize_fontname (name)
limit = limit or fuzzy_limit
- if not fonts_loaded then names.data = load_names() end
-
- local data = names.data
-
- if type(data) ~= "table" then
+ if not name_index then name_index = load_names () end
+ if not name_index or type (name_index) ~= "table" then
if not fonts_reloaded then
return reload_db("no database", find_closest, name)
end
return false
end
+
local by_distance = { } --- (int, string list) dict
local distances = { } --- int list
local cached = { } --- (string, int) dict
- local mappings = data.mappings
+ local mappings = name_index.mappings
local n_fonts = #mappings
for n = 1, n_fonts do
local current = mappings[n]
- local cnames = current.sanitized
--[[
This is simplistic but surpisingly fast.
Matching is performed against the “fullname” field
@@ -1119,23 +1200,22 @@ find_closest = function (name, limit)
font name categories as well as whatever agrep
does.
--]]
- if cnames then
- local fullname, sfullname = current.fullname, cnames.fullname
+ local fullname = current.plainname
+ local sfullname = current.fullname
+ local dist = cached[sfullname]--- maybe already calculated
- local dist = cached[sfullname]--- maybe already calculated
- if not dist then
- dist = iterative_levenshtein(name, sfullname)
- cached[sfullname] = dist
- end
- local namelst = by_distance[dist]
- if not namelst then --- first entry
- namelst = { fullname }
- distances[#distances+1] = dist
- else --- append
- namelst[#namelst+1] = fullname
- end
- by_distance[dist] = namelst
+ if not dist then
+ dist = iterative_levenshtein(name, sfullname)
+ cached[sfullname] = dist
+ end
+ local namelst = by_distance[dist]
+ if not namelst then --- first entry
+ namelst = { fullname }
+ distances[#distances+1] = dist
+ else --- append
+ namelst[#namelst+1] = fullname
end
+ by_distance[dist] = namelst
end
--- print the matches according to their distance
@@ -1160,119 +1240,225 @@ find_closest = function (name, limit)
return false
end --- find_closest()
-local sanitize_names = function (names)
- local res = { }
- for idx, name in next, names do
- res[idx] = sanitize_string(name)
- end
- return res
-end
-
local load_font_file = function (filename, subfont)
local rawfont, _msg = fontloaderopen (filename, subfont)
if not rawfont then
report ("log", 1, "db", "ERROR: failed to open %s", filename)
return
end
+
local metadata = fontloaderto_table (rawfont)
fontloaderclose (rawfont)
+
+ metadata.glyphs = nil
+ metadata.subfonts = nil
+ metadata.gpos = nil
+ metadata.gsub = nil
+ metadata.lookups = nil
+
collectgarbage "collect"
+
return metadata
end
---[[doc--
-The data inside an Opentype font file can be quite heterogeneous.
-Thus in order to get the relevant information, parts of the original
-table as returned by the font file reader need to be relocated.
---doc]]--
+--- rawdata -> (int * int * int | bool)
---- string -> int -> bool -> string -> fontentry
-ot_fullinfo = function (filename, subfont, texmf, basename)
- local namedata = { }
+local get_size_info = function (metadata)
+ local design_size = metadata.design_size
+ local design_range_top = metadata.design_range_top
+ local design_range_bottom = metadata.design_range_bottom
- local metadata = load_font_file (filename, subfont)
- if not metadata then
- return nil
+ local fallback_size = design_size ~= 0 and design_size
+ or design_range_bottom ~= 0 and design_range_bottom
+ or design_range_top ~= 0 and design_range_top
+
+ if fallback_size then
+ design_size = (design_size or fallback_size) / 10
+ design_range_top = (design_range_top or fallback_size) / 10
+ design_range_bottom = (design_range_bottom or fallback_size) / 10
+ return {
+ design_size, design_range_top, design_range_bottom,
+ }
end
+ return false
+end
+
+local get_english_names = function (names, basename)
+
local english_names
- if metadata.names then
- for _, raw_namedata in next, metadata.names do
+ if names then
+ for _, raw_namedata in next, names do
if raw_namedata.lang == "English (US)" then
english_names = raw_namedata.names
end
end
else
- -- no names table, propably a broken font
+ -- no names table, probably a broken font
report("log", 1, "db",
"Broken font %s rejected due to missing names table.",
basename)
- return
+ return nil
end
+ return english_names
+end
+
+local organize_namedata = function (metadata,
+ english_names,
+ basename,
+ info)
+
+ --print (english_names.family, "<>", english_names.preffamilyname)
local fontnames = {
--- see
--- https://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html
- fullname = english_names.compatfull
- or english_names.fullname,
- family = english_names.preffamilyname
- or english_names.family,
- prefmodifiers = english_names.prefmodifiers,
- subfamily = english_names.subfamily,
- psname = english_names.postscriptname,
- pfullname = metadata.fullname,
- fontname = metadata.fontname,
- metafamily = metadata.familyname,
+ --- http://www.microsoft.com/typography/OTSPEC/name.htm#NameIDs
+ english = {
+ --- where a “compatfull” field is given, the value of “fullname” is
+ --- either identical or differs by separating the style
+ --- with a hyphen and omitting spaces. (According to the
+ --- spec, “compatfull” is “Macintosh only”.)
+ --- Of the three “fullname” fields, this one appears to be the one
+ --- with the entire name given in a legible,
+ --- non-abbreviated fashion, for most fonts at any rate.
+ --- However, in some fonts (e.g. CMU) all three fields are
+ --- identical.
+ fullname = english_names.compatfull
+ or english_names.fullname,
+ --- we keep both the “preferred family” and the “family”
+ --- values around since both are valid but can turn out
+ --- quite differently, e.g. with Latin Modern:
+ --- preffamily: “Latin Modern Sans”,
+ --- family: “LM Sans 10”
+ preffamily = english_names.preffamilyname,
+ family = english_names.family,
+ prefmodifiers = english_names.prefmodifiers,
+ subfamily = english_names.subfamily,
+ psname = english_names.postscriptname,
+ },
+
+ metadata = {
+ fullname = metadata.fullname,
+ fontname = metadata.fontname,
+ metafamily = metadata.familyname,
+ },
+
+ info = {
+ fullname = info.fullname,
+ familyname = info.familyname,
+ fontname = info.fontname,
+ },
}
-- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size
if metadata.fontstyle_name then
+ --- not present in all fonts, often differs from the preferred
+ --- subfamily as well as subfamily fields, e.g. with LMSans10-BoldOblique:
+ --- subfamily: “Bold Italic”
+ --- prefmodifiers: “10 Bold Oblique”
+ --- fontstyle_name: “Bold Oblique”
for _, name in next, metadata.fontstyle_name do
if name.lang == 1033 then --- I hate magic numbers
fontnames.fontstyle_name = name.name
end
end
+
+ --print (fontnames.metadata.fontname, "|>", english_names.subfamily, "<>", english_names.prefmodifiers)
+ --print (fontnames.metadata.fontname, "|>", english_names.subfamily, "<>", fontnames.fontstyle_name)
end
- namedata.sanitized = sanitize_names (fontnames)
- namedata.fontname = metadata.fontname
- namedata.fullname = metadata.fullname
- namedata.familyname = metadata.familyname
- namedata.weight = metadata.pfminfo.weight
- namedata.width = metadata.pfminfo.width
- namedata.slant = metadata.italicangle
+ return {
+ sanitized = sanitize_fontnames (fontnames),
+ fontname = metadata.fontname,
+ fullname = metadata.fullname,
+ familyname = metadata.familyname,
+ }
+
+end
+
+
+local dashsplitter = lpegsplitat "-"
+
+local split_fontname = function (fontname)
+ --- sometimes the style hides in the latter part of the
+ --- fontname, separated by a dash, e.g. “Iwona-Regular”,
+ --- “GFSSolomos-Regular”
+ local splitted = { lpegmatch (dashsplitter, fontname) }
+ if next (splitted) then
+ return sanitize_fontname (splitted [#splitted])
+ end
+end
+
+local organize_styledata = function (fontname, metadata, english_names, info)
+ local pfminfo = metadata.pfminfo
+ local names = metadata.names
+
+-- print (">", pfminfo.avgwidth,
+-- (metadata.italicangle == info.italicangle) and "T" or
+-- string.format ("%f / %f", metadata.italicangle, info.italicangle),
+-- pfminfo.width, pfminfo.weight, info.weight)
+ return {
+ -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size
+ size = get_size_info (metadata),
+ weight = {
+ pfminfo.weight, -- integer (multiple of 100?)
+ sanitize_fontname (info.weight), -- style name
+ },
+ split = split_fontname (fontname),
+ width = pfminfo.width,
+ italicangle = metadata.italicangle,
+-- italicangle = {
+-- metadata.italicangle, -- float
+-- info.italicangle, -- truncated to integer point size?
+-- },
--- this is for querying, see www.ntg.nl/maps/40/07.pdf for details
- namedata.units_per_em = metadata.units_per_em
- namedata.version = metadata.version
- -- don't waste the space with zero values
+ units_per_em = metadata.units_per_em,
+ version = metadata.version,
+ }
+end
- local design_size = metadata.design_size
- local design_range_top = metadata.design_range_top
- local design_range_bottom = metadata.design_range_bottom
+--[[doc--
+The data inside an Opentype font file can be quite heterogeneous.
+Thus in order to get the relevant information, parts of the original
+table as returned by the font file reader need to be relocated.
+--doc]]--
- local fallback_size = design_size ~= 0 and design_size
- or design_range_bottom ~= 0 and design_range_bottom
- or design_range_top ~= 0 and design_range_top
+--- string -> int -> bool -> string -> fontentry
- if fallback_size then
- design_size = (design_size or fallback_size) / 10
- design_range_top = (design_range_top or fallback_size) / 10
- design_range_bottom = (design_range_bottom or fallback_size) / 10
- namedata.size = {
- design_size, design_range_top, design_range_bottom,
- }
- else
- namedata.size = false
+ot_fullinfo = function (filename,
+ subfont,
+ location,
+ basename,
+ format,
+ info)
+
+ local metadata = load_font_file (filename, subfont)
+ if not metadata then
+ return nil
end
- --- file location data (used to be filename field)
- namedata.filename = filename --> sys
- namedata.basename = basename --> texmf
- namedata.texmf = texmf or false
- namedata.subfont = subfont
+ local english_names = get_english_names (metadata.names, basename)
+ local namedata = organize_namedata (metadata,
+ english_names,
+ basename,
+ info)
+ local style = organize_styledata (namedata.fontname,
+ metadata,
+ english_names,
+ info)
- return namedata
+ return {
+ file = { base = basename,
+ full = filename,
+ subfont = subfont,
+ location = location or "system" },
+ format = format,
+ names = namedata,
+ style = style,
+ version = metadata.version,
+ }
end
--[[doc--
@@ -1286,7 +1472,8 @@ end
--doc]]--
--- string -> int -> bool -> string -> fontentry
-t1_fullinfo = function (filename, _subfont, texmf, basename)
+
+t1_fullinfo = function (filename, _subfont, location, basename, format)
local namedata = { }
local metadata = load_font_file (filename)
@@ -1308,7 +1495,7 @@ t1_fullinfo = function (filename, _subfont, texmf, basename)
local style_synonyms_set = style_synonyms.set
if weight then
- weight = sanitize_string (weight)
+ weight = sanitize_fontname (weight)
local tmp = ""
if style_synonyms_set.bold[weight] then
tmp = "bold"
@@ -1329,7 +1516,7 @@ t1_fullinfo = function (filename, _subfont, texmf, basename)
--- else italic
end
- namedata.sanitized = sanitize_names ({
+ namedata.sanitized = sanitize_fontnames ({
fontname = fontname,
psname = fullname,
pfullname = fullname,
@@ -1353,8 +1540,10 @@ t1_fullinfo = function (filename, _subfont, texmf, basename)
namedata.filename = filename --> sys
namedata.basename = basename --> texmf
- namedata.texmf = texmf or false
+ namedata.format = format
+ namedata.location = location or "system"
namedata.subfont = false
+
return namedata
end
@@ -1368,62 +1557,146 @@ local loaders = {
pfa = t1_fullinfo,
}
---- we return true if the fond is new or re-indexed
+--- not side-effect free!
+
+local compare_timestamps = function (fullname,
+ currentstatus,
+ currententrystatus,
+ currentmappings,
+ targetstatus,
+ targetentrystatus,
+ targetmappings)
+
+ local currenttimestamp = currententrystatus
+ and currententrystatus.timestamp
+ local targettimestamp = lfsattributes (fullname, "modification")
+
+ if targetentrystatus ~= nil
+ and targetentrystatus.timestamp == targettimestamp then
+ report ("log", 3, "db", "Font %q already read.", fullname)
+ return false
+ end
+
+ targetentrystatus.timestamp = targettimestamp
+ targetentrystatus.index = targetentrystatus.index or { }
+
+ if currenttimestamp == targettimestamp
+ and not targetentrystatus.index [1]
+ then
+ --- copy old namedata into new
+
+ for _, currentindex in next, currententrystatus.index do
+
+ local targetindex = #targetentrystatus.index
+ local fullinfo = currentmappings [currentindex]
+ local location = #targetmappings + 1
+
+ targetmappings [location] = fullinfo
+ targetentrystatus.index [targetindex + 1] = location
+ end
+
+ report ("log", 3, "db", "Font %q already indexed.", fullname)
+
+ return false
+ end
+
+ return true
+end
+
+local insert_fullinfo = function (fullname,
+ basename,
+ n_font,
+ loader,
+ format,
+ location,
+ targetmappings,
+ targetentrystatus,
+ info)
+
+ local subfont
+ if n_font ~= false then
+ subfont = n_font - 1
+ else
+ subfont = false
+ n_font = 1
+ end
+
+ local fullinfo = loader (fullname, subfont,
+ location, basename,
+ format, info)
+
+ if not fullinfo then
+ return false
+ end
+
+ local index = targetentrystatus.index [n_font]
+
+ if not index then
+ index = #targetmappings + 1
+ end
+
+ targetmappings [index] = fullinfo
+ targetentrystatus.index [n_font] = index
+
+ return true
+end
+
+
+
+--- we return true if the font is new or re-indexed
--- string -> dbobj -> dbobj -> bool
-local load_font = function (fullname, fontnames, newfontnames, texmf)
- local newmappings = newfontnames.mappings
- local newstatus = newfontnames.status --- by full path
- local mappings = fontnames.mappings
- local status = fontnames.status
+local read_font_names = function (fullname,
+ currentnames,
+ targetnames,
+ location)
+
+ local targetmappings = targetnames.mappings
+ local targetstatus = targetnames.status --- by full path
+ local targetentrystatus = targetstatus [fullname]
+
+ if targetentrystatus == nil then
+ targetentrystatus = { }
+ targetstatus [fullname] = targetentrystatus
+ end
- local basename = filebasename(fullname)
- local barename = filenameonly(fullname)
+ local currentmappings = currentnames.mappings
+ local currentstatus = currentnames.status
+ local currententrystatus = currentstatus [fullname]
- local format = stringlower (filesuffix (basename))
+ local basename = filebasename (fullname)
+ local barename = filenameonly (fullname)
+ local entryname = fullname
- local entryname = fullname
- if texmf == true then
+ if location == "texmf" then
entryname = basename
end
- if names.blacklist[fullname] or names.blacklist[basename]
- then
+ --- 1) skip if blacklisted
+
+ if names.blacklist[fullname] or names.blacklist[basename] then
report("log", 2, "db",
"Ignoring blacklisted font %q", fullname)
return false
end
- local new_timestamp, current_timestamp
- current_timestamp = status[fullname]
- and status[fullname].timestamp
- new_timestamp = lfsattributes(fullname, "modification")
+ --- 2) skip if known with same timestamp
- local newentrystatus = newstatus[fullname]
- --- newentrystatus: nil | false | table
- if newentrystatus and newentrystatus.timestamp == new_timestamp then
- -- already indexed this run
+ if not compare_timestamps (fullname,
+ currentstatus,
+ currententrystatus,
+ currentmappings,
+ targetstatus,
+ targetentrystatus,
+ targetmappings)
+ then
return false
end
- newstatus[fullname] = newentrystatus or { }
- local newentrystatus = newstatus[fullname]
- newentrystatus.timestamp = new_timestamp
- newentrystatus.index = newentrystatus.index or { }
+ --- 3) new font; choose a loader, abort if unknown
- if current_timestamp == new_timestamp
- and not newentrystatus.index[1]
- then
- for _, v in next, status[fullname].index do
- local index = #newentrystatus.index
- local fullinfo = mappings[v]
- local location = #newmappings + 1
- newmappings[location] = fullinfo --- keep
- newentrystatus.index[index+1] = location --- is this actually used anywhere?
- end
- report("log", 2, "db", "Font %q already indexed", basename)
- return false
- end
+ local format = stringlower (filesuffix (basename))
+ local loader = loaders [format] --- ot_fullinfo, t1_fullinfo
local loader = loaders[format] --- ot_fullinfo, t1_fullinfo
if not loader then
@@ -1432,39 +1705,40 @@ local load_font = function (fullname, fontnames, newfontnames, texmf)
return false
end
- local info = fontloaderinfo(fullname)
- if info then
- if type(info) == "table" and #info > 1 then --- ttc
- for n_font = 1, #info do
- local fullinfo = loader (fullname, n_font-1, texmf, basename)
- if not fullinfo then
- return false
- end
- local location = #newmappings+1
- local index = newentrystatus.index[n_font]
- if not index then index = location end
+ --- 4) get basic info, abort if fontloader can’t read it
- newmappings[index] = fullinfo
- newentrystatus.index[n_font] = index
- end
- else
- local fullinfo = loader (fullname, false, texmf, basename)
- if not fullinfo then
- return false
- end
- local location = #newmappings+1
- local index = newentrystatus.index[1]
- if not index then index = location end
+ local info = fontloaderinfo (fullname)
- newmappings[index] = fullinfo
- newentrystatus.index[1] = index
+ if not info then
+ report ("log", 1, "db",
+ "Failed to read basic information from %q", basename)
+ return false
+ end
+
+
+ --- 5) check for subfonts and process each of them
+
+ if type (info) == "table" and #info > 1 then --- ttc
+
+ local success = false --- true if at least one subfont got read
+
+ for n_font = 1, #info do
+ if insert_fullinfo (fullname, basename, n_font,
+ loader, format, location,
+ targetmappings, targetentrystatus,
+ info)
+ then
+ success = true
+ end
end
- else --- missing info
- report("log", 1, "db", "Failed to load %q", basename)
- return false
+ return success
end
- return true
+
+ return insert_fullinfo (fullname, basename, false,
+ loader, format, location,
+ targetmappings, targetentrystatus,
+ info)
end
local path_normalize
@@ -1542,7 +1816,7 @@ local create_blacklist = function (blacklist, whitelist)
local result = { }
local dirs = { }
- report("info", 1, "db", "Blacklisting %q files and directories",
+ report("info", 2, "db", "Blacklisting %d files and directories",
#blacklist)
for i=1, #blacklist do
local entry = blacklist[i]
@@ -1553,7 +1827,7 @@ local create_blacklist = function (blacklist, whitelist)
end
end
- report("info", 1, "db", "Whitelisting %q files", #whitelist)
+ report("info", 2, "db", "Whitelisting %d files", #whitelist)
for i=1, #whitelist do
result[whitelist[i]] = nil
end
@@ -1677,7 +1951,7 @@ do
end
--- initialize
- set_font_filter (config.luaotfload.formats)
+ set_font_filter (luaotfloadconfig.formats)
end
local process_dir_tree
@@ -1780,29 +2054,30 @@ end
scan_dir() scans a directory and populates the list of fonts
with all the fonts it finds.
- · dirname : name of the directory to scan
- · fontnames : current font db object
- · newnames : font db object to fill
- · dry_run : don’t touch anything
+ · dirname : name of the directory to scan
+ · currentnames : current font db object
+ · targetnames : font db object to fill
+ · dry_run : don’t touch anything
--doc]]--
--- string -> dbobj -> dbobj -> bool -> bool -> (int * int)
-local scan_dir = function (dirname, fontnames, newfontnames,
- dry_run, texmf)
+
+local scan_dir = function (dirname, currentnames, targetnames,
+ dry_run, location)
if lpegmatch (p_blacklist, dirname) then
- report ("both", 3, "db",
+ report ("both", 4, "db",
"Skipping blacklisted directory %s", dirname)
--- ignore
return 0, 0
end
- local found = find_font_files (dirname, texmf ~= true)
+ local found = find_font_files (dirname, location ~= "texmf")
if not found then
- report ("both", 3, "db",
+ report ("both", 4, "db",
"No such directory: %q; skipping.", dirname)
return 0, 0
end
- report ("both", 3, "db", "Scanning directory %s", dirname)
+ report ("both", 4, "db", "Scanning directory %s", dirname)
local n_new = 0 --- total of fonts collected
local n_found = #found
@@ -1853,7 +2128,8 @@ local path_separator = ostype == "windows" and ";" or ":"
--doc]]--
--- dbobj -> dbobj -> bool? -> (int * int)
-local scan_texmf_fonts = function (fontnames, newfontnames, dry_run)
+
+local scan_texmf_fonts = function (currentnames, targetnames, dry_run)
local n_scanned, n_new, fontdirs = 0, 0
local osfontdir = kpseexpand_path "$OSFONTDIR"
@@ -1882,8 +2158,8 @@ local scan_texmf_fonts = function (fontnames, newfontnames, dry_run)
"Initiating scan of %d directories.", #tasks)
report_status_start (2, 4)
for _, d in next, tasks do
- local found, new = scan_dir (d, fontnames, newfontnames,
- dry_run, true)
+ local found, new = scan_dir (d, currentnames, targetnames,
+ dry_run, "texmf")
n_scanned = n_scanned + found
n_new = n_new + new
end
@@ -2048,6 +2324,10 @@ do --- closure for read_fonts_conf()
--- We exclude paths with texmf in them, as they should be
--- found anyway; also duplicates are ignored by checking
--- if they are elements of dirs_done.
+ ---
+ --- FIXME does this mean we cannot access paths from
+ --- distributions (e.g. Context minimals) installed
+ --- separately?
if not (stringfind(path, "texmf") or dirs_done[path]) then
acc[#acc+1] = path
dirs_done[path] = true
@@ -2159,18 +2439,19 @@ end
--doc]]--
--- dbobj -> dbobj -> bool? -> (int * int)
-local scan_os_fonts = function (fontnames, newfontnames,
+local scan_os_fonts = function (currentnames,
+ targetnames,
dry_run)
local n_scanned, n_new = 0, 0
- report ("info", 1, "db", "Scanning OS fonts...")
+ report ("info", 2, "db", "Scanning OS fonts...")
report ("info", 3, "db",
"Searching in static system directories...")
report_status_start (2, 4)
for _, d in next, get_os_dirs () do
- local found, new = scan_dir (d, fontnames,
- newfontnames, dry_run)
+ local found, new = scan_dir (d, currentnames,
+ targetnames, dry_run)
n_scanned = n_scanned + found
n_new = n_new + new
end
@@ -2187,37 +2468,63 @@ flush_lookup_cache = function ()
return true, names.lookups
end
---- dbobj -> dbobj
-local gen_fast_lookups = function (fontnames)
- report("both", 1, "db", "Creating filename map")
- local mappings = fontnames.mappings
+
+--- fontentry list -> filemap
+
+local generate_filedata = function (mappings)
+
+ report ("both", 2, "db", "Creating filename map.")
+
local nmappings = #mappings
- --- this is needlessly complicated due to texmf priorization
- local filenames = {
- bare = { },
- base = { },
+
+ local files = {
+ bare = {
+ ["local"] = { },
+ system = { }, --- mapped to mapping format -> index in full
+ texmf = { }, --- mapped to mapping format -> “true”
+ },
+ base = {
+ ["local"] = { },
+ system = { }, --- mapped to index in “full”
+ texmf = { }, --- set; all values are “true”
+ },
full = { }, --- non-texmf
}
- local texmf, sys = { }, { } -- quintuple list
-
- for idx = 1, nmappings do
- local entry = mappings[idx]
- local filename = entry.filename
- local basename = entry.basename
- local bare = filenameonly(filename)
- local subfont = entry.subfont
+ local base = files.base
+ local bare = files.bare
+ local full = files.full
- entry.index = idx
---- unfortunately, the sys/texmf schism prevents us from
---- doing away the full name, so we cannot avoid the
---- substantial duplication
--- entry.filename = nil
+ local conflicts = {
+ basenames = 0,
+ barenames = 0,
+ }
- if entry.texmf == true then
- texmf[#texmf+1] = { idx, basename, bare, true, nil }
+ for index = 1, nmappings do
+ local entry = mappings [index]
+
+ local filedata = entry.file
+ local format
+ local location
+ local fullpath
+ local basename
+ local barename
+ local subfont
+
+ if filedata then --- new entry
+ format = entry.format --- otf, afm, ...
+ location = filedata.location --- texmf, system, ...
+ fullpath = filedata.full
+ basename = filedata.base
+ barename = filenameonly (fullpath)
+ subfont = filedata.subfont
else
- sys[#sys+1] = { idx, basename, bare, false, filename }
+ format = entry.format --- otf, afm, ...
+ location = entry.location --- texmf, system, ...
+ fullpath = entry.fullpath
+ basename = entry.basename
+ barename = filenameonly (fullpath)
+ subfont = entry.subfont
end
end
@@ -2226,104 +2533,629 @@ local gen_fast_lookups = function (fontnames)
for i=1, #lst do
local idx, base, bare, intexmf, full = unpack(lst[i])
- local known = filenames.base[base] or filenames.bare[bare]
- if known then --- known
- report("both", 3, "db",
- "Font file %q already indexed (%d)",
- base, idx)
- report("both", 3, "db", "> old location: %s",
- (filenames.full[known] or "texmf"))
- report("both", 3, "db", "> new location: %s",
- (intexmf and "texmf" or full))
+ entry.index = index
+
+ --- 1) add to basename table
+
+ local inbase = base [location] --- no format since the suffix is known
+
+ if inbase then
+ local present = inbase [basename]
+ if present then
+ report ("both", 4, "db",
+ "Conflicting basename: %q already indexed \z
+ in category %s, ignoring.",
+ barename, location)
+ conflicts.basenames = conflicts.basenames + 1
+
+ --- track conflicts per font
+ local conflictdata = entry.conflicts
+
+ if not conflictdata then
+ entry.conflicts = { basename = present }
+ else -- some conflicts already detected
+ conflictdata.basename = present
+ end
+
+ else
+ inbase [basename] = index
end
+ else
+ inbase = { basename = index }
+ base [location] = inbase
+ end
+
+ --- 2) add to barename table
+
+ local inbare = bare [location] [format]
+
+ if inbare then
+ local present = inbare [barename]
+ if present then
+ report ("both", 4, "db",
+ "Conflicting barename: %q already indexed \z
+ in category %s/%s, ignoring.",
+ barename, location, format)
+ conflicts.barenames = conflicts.barenames + 1
+
+ --- track conflicts per font
+ local conflictdata = entry.conflicts
+
+ if not conflictdata then
+ entry.conflicts = { barename = present }
+ else -- some conflicts already detected
+ conflictdata.barename = present
+ end
- filenames.bare[bare] = idx
- filenames.base[base] = idx
- if intexmf == true then
- filenames.full[idx] = nil
else
- filenames.full[idx] = full
+ inbare [barename] = index
end
+ else
+ inbare = { [barename] = index }
+ bare [location] [format] = inbare
+ end
+
+ --- 3) add to fullpath map
+
+ full [index] = fullpath
+ end
+
+ --- TODO adapt to new mechanism!
+-- if luaotfloadconfig.prioritize == "texmf" then
+-- report("both", 2, "db", "Preferring texmf fonts")
+-- addmap(sys)
+-- addmap(texmf)
+-- else --- sys
+-- addmap(texmf)
+-- addmap(sys)
+-- end
+
+ return files
+end
+
+local match_synonyms = function (pattern)
+ local nopattern = 1 - pattern
+ return nopattern^0 * pattern * Cc (true) + Cc (false)
+end
+
+local determine_italic
+local determine_bold
+local pick_style
+local check_regular
+
+do
+ local italic = match_synonyms (P"oblique" + P"slanted" + P"italic")
+ local bold = match_synonyms (P"bold" + P"demi", P"heavy", P"black", P"ultra")
+
+ determine_italic = function (fontstyle_name,
+ italicangle,
+ prefmodifiers,
+ subfamily)
+ if italicangle ~= 0 then
+ return true
+ elseif fontstyle_name and lpegmatch (italic, fontstyle_name) then
+ return true
+ elseif prefmodifiers and lpegmatch (italic, prefmodifiers) then
+ return lpegmatch (italic, prefmodifiers)
+ else
+ return lpegmatch (italic, subfamily)
+ end
+ end
+
+ determine_bold = function (fontstyle_name,
+ weight,
+ prefmodifiers,
+ subfamily)
+ if weight [2] == "bold" then
+ return true
+ elseif fontstyle_name and lpegmatch (bold, fontstyle_name) then
+ return true
+ elseif prefmodifiers and lpegmatch (bold, prefmodifiers) then
+ return true
+ else
+ return lpegmatch (bold, subfamily)
+ end
+ end
+
+ local splitfontname = lpeg.splitat "-"
+
+ local choose_exact = function (field)
+ if field == "italic" or field == "oblique" then
+ return "i"
+ elseif field == "bold" then
+ return "b"
+ elseif field == "bolditalic" or field == "boldoblique" then
+ return "bi"
+ end
+
+ return false
+ end
+
+ pick_style = function (fontstyle_name,
+ prefmodifiers,
+ subfamily,
+ splitstyle)
+ local style
+ if fontstyle_name ~= nil then
+ style = choose_exact (prefmodifiers)
+ end
+ if not style and prefmodifiers ~= nil then
+ style = choose_exact (prefmodifiers)
end
+ if not style then
+ style = choose_exact (subfamily)
+ end
+ if not style and splitstyle ~= nil then
+ choose_exact (splitstyle)
+ end
+ return style
+ end
+
+ --- we use only exact matches here since there are constructs
+ --- like “regularitalic” (Cabin, Bodoni Old Fashion)
+
+ check_regular = function (fontstyle_name,
+ prefmodifiers,
+ subfamily,
+ splitstyle)
+
+ if regular_synonym [fontstyle_name]
+ or regular_synonym [prefmodifiers]
+ or regular_synonym [subfamily]
+ or regular_synonym [splitstyle]
+ then
+ return "r"
+ end
+
+ return nil
end
+end
- if config.luaotfload.prioritize == "texmf" then
- report("both", 1, "db", "Preferring texmf fonts")
- addmap(sys)
- addmap(texmf)
- else --- sys
- addmap(texmf)
- addmap(sys)
+local pull_values = function (entry)
+ local file = entry.file
+ local names = entry.names
+ local style = entry.style
+ local sanitized = names.sanitized
+ local english = sanitized.english
+ local info = sanitized.info
+
+ --- pull file info ...
+ entry.basename = file.base
+ entry.fullpath = file.full
+ entry.location = file.location
+ entry.subfont = file.subfont
+
+ --- pull name info ...
+ entry.psname = english.psname
+ entry.fontname = info.fontname
+ entry.fullname = english.fullname or info.fullname
+ entry.familyname = english.preffamily
+ or english.family
+ or info.familyname
+ entry.fontstyle_name = sanitized.fontstyle_name
+ entry.plainname = names.fullname
+ entry.prefmodifiers = english.prefmodifiers
+ entry.subfamily = english.subfamily
+
+ --- pull style info ...
+ entry.italicangle = style.italicangle
+ entry.size = style.size
+ entry.splitstyle = style.split
+ entry.weight = style.weight
+
+ if luaotfloadconfig.strip == true then
+ --if false then
+ entry.file = nil
+ entry.names = nil
+ entry.style = nil
+ end
+end
+
+local collect_families = function (mappings)
+
+ report ("info", 2, "db", "Analyzing families, sizes, and styles.")
+
+ local families = {
+ ["local"] = { },
+ system = { },
+ texmf = { },
+ }
+
+ for i = 1, #mappings do
+
+ local entry = mappings [i]
+
+ if entry.file then
+ pull_values (entry)
+ end
+
+ local location = entry.location
+ local format = entry.format
+
+ local subtable = families [location] [format]
+ if not subtable then
+ subtable = { }
+ families [location] [format] = subtable
+ end
+
+ --local fullname = english.fullname or info.fullname
+ --local fontname = info.fontname
+
+ local familyname = entry.familyname
+ local fontstyle_name = entry.fontstyle_name
+ local prefmodifiers = entry.prefmodifiers
+ local subfamily = entry.subfamily
+
+ local weight = entry.weight
+ local italicangle = entry.italicangle
+ local splitstyle = entry.splitstyle
+
+ local modifier = pick_style (fontstyle_name,
+ prefmodifiers,
+ subfamily,
+ splitstyle)
+
+ if not modifier then --- guess
+ local italic = determine_italic (fontstyle_name,
+ italicangle,
+ prefmodifiers,
+ subfamily)
+ local bold = determine_bold (fontstyle_name,
+ weight,
+ prefmodifiers,
+ subfamily)
+ if bold and italic then
+ modifier = "bi"
+ elseif bold then
+ modifier = "b"
+ elseif italic then
+ modifier = "i"
+ end
+ end
+
+ if not modifier then --- regular, exact only
+ modifier = check_regular (fontstyle_name,
+ subfamily,
+ splitstyle)
+ end
+
+ if modifier then
+ --- stub; here we will continue building a list of optical sizes
+ --- no size -> hash “normal”
+ --- other sizes -> indexed tuples { dsnsize, high, low, idx }
+ local familytable = subtable [familyname]
+ if not familytable then
+ familytable = { }
+ subtable [familyname] = familytable
+ end
+
+ --- the style table is treated as an unordered list
+ local styletable = familytable [modifier]
+ if not styletable then
+ styletable = { }
+ familytable [modifier] = styletable
+ end
+
+ if not prefmodifiers then --- default size for this style/family combo
+ styletable.default = entry.index
+ end
+
+ local size = entry.size --- dsnsize * high * low
+ if size then
+ styletable [#styletable + 1] = {
+ size [1],
+ size [2],
+ size [3],
+ entry.index,
+ }
+ else
+ styletable.default = entry.index
+ end
+ end
end
- fontnames.filenames = filenames
- texmf, sys = nil, nil
collectgarbage "collect"
- return fontnames
+ return families
+end
+
+local cmp_sizes = function (a, b)
+ return a [1] < b [1]
+end
+
+local order_design_sizes = function (families)
+
+ report ("info", 2, "db", "Ordering design sizes.")
+
+ for location, data in next, families do
+ for format, data in next, data do
+ for familyname, data in next, data do
+ for style, data in next, data do
+ tablesort (data, cmp_sizes)
+ end
+ end
+ end
+ end
+
+ return families
+end
+
+local retrieve_namedata = function (currentnames,
+ targetnames,
+ dry_run,
+ n_rawnames,
+ n_newnames)
+
+ local rawnames, new = scan_texmf_fonts (currentnames,
+ targetnames,
+ dry_run)
+
+ n_rawnames = n_rawnames + rawnames
+ n_newnames = n_newnames + new
+
+ rawnames, new = scan_os_fonts (currentnames, targetnames, dry_run)
+
+ n_rawnames = n_rawnames + rawnames
+ n_newnames = n_newnames + new
+
+ return n_rawnames, n_newnames
+end
+
+
+--- dbobj -> stats
+
+local collect_statistics = function (mappings)
+ local sum_dsnsize, n_dsnsize = 0, 0
+
+ local fullname, family, families = { }, { }, { }
+ local subfamily, prefmodifiers, fontstyle_name = { }, { }, { }
+
+ local addtohash = function (hash, item)
+ if item then
+ local times = hash [item]
+ if times then
+ hash [item] = times + 1
+ else
+ hash [item] = 1
+ end
+ end
+ end
+
+ local appendtohash = function (hash, key, value)
+ if key and value then
+ local entry = hash [key]
+ if entry then
+ entry [#entry + 1] = value
+ else
+ hash [key] = { value }
+ end
+ end
+ end
+
+ local addtoset = function (hash, key, value)
+ if key and value then
+ local set = hash [key]
+ if set then
+ set [value] = true
+ else
+ hash [key] = { [value] = true }
+ end
+ end
+ end
+
+ local setsize = function (set)
+ local n = 0
+ for _, _ in next, set do
+ n = n + 1
+ end
+ return n
+ end
+
+ local hashsum = function (hash)
+ local n = 0
+ for _, m in next, hash do
+ n = n + m
+ end
+ return n
+ end
+
+ for _, entry in next, mappings do
+ local style = entry.style
+ local names = entry.names.sanitized
+ local englishnames = names.english
+
+ addtohash (fullname, englishnames.fullname)
+ addtohash (family, englishnames.family)
+ addtohash (subfamily, englishnames.subfamily)
+ addtohash (prefmodifiers, englishnames.prefmodifiers)
+ addtohash (fontstyle_name, names.fontstyle_name)
+
+ addtoset (families, englishnames.family, englishnames.fullname)
+
+ local sizeinfo = entry.style.size
+ if sizeinfo then
+ sum_dsnsize = sum_dsnsize + sizeinfo [1]
+ n_dsnsize = n_dsnsize + 1
+ end
+ end
+
+ --inspect (families)
+
+ local n_fullname = setsize (fullname)
+ local n_family = setsize (family)
+
+ if logs.get_loglevel () > 1 then
+ local pprint_top = function (hash, n, set)
+
+ local freqs = { }
+ local items = { }
+
+ for item, value in next, hash do
+ if set then
+ freq = setsize (value)
+ else
+ freq = value
+ end
+ local ifreq = items [freq]
+ if ifreq then
+ ifreq [#ifreq + 1] = item
+ else
+ items [freq] = { item }
+ freqs [#freqs + 1] = freq
+ end
+ end
+
+ tablesort (freqs)
+
+ local from = #freqs
+ local to = from - (n - 1)
+ if to < 1 then
+ to = 1
+ end
+
+ for i = from, to, -1 do
+ local freq = freqs [i]
+ local itemlist = items [freq]
+
+ if type (itemlist) == "table" then
+ itemlist = tableconcat (itemlist, ", ")
+ end
+
+ report ("both", 0, "db",
+ " · %4d × %s.",
+ freq, itemlist)
+ end
+ end
+
+ report ("both", 0, "", "~~~~ font index statistics ~~~~")
+ report ("both", 0, "db",
+ " · Collected %d fonts (%d names) in %d families.",
+ #mappings, n_fullname, n_family)
+ pprint_top (families, 4, true)
+
+ report ("both", 0, "db",
+ " · %d different “subfamily” kinds",
+ setsize (subfamily))
+ pprint_top (subfamily, 4)
+
+ report ("both", 0, "db",
+ " · %d different “prefmodifiers” kinds",
+ setsize (prefmodifiers))
+ pprint_top (prefmodifiers, 4)
+
+ report ("both", 0, "db",
+ " · %d different “fontstyle_name” kinds",
+ setsize (fontstyle_name))
+ pprint_top (fontstyle_name, 4)
+ end
+
+ local mean_dsnsize = 0
+ if n_dsnsize > 0 then
+ mean_dsnsize = sum_dsnsize / n_dsnsize
+ end
+
+ return {
+ mean_dsnsize = mean_dsnsize,
+ names = {
+ fullname = n_fullname,
+ families = n_family,
+ },
+-- style = {
+-- subfamily = subfamily,
+-- prefmodifiers = prefmodifiers,
+-- fontstyle_name = fontstyle_name,
+-- },
+ }
end
--- force: dictate rebuild from scratch
--- dry_dun: don’t write to the db, just scan dirs
--- dbobj? -> bool? -> bool? -> dbobj
-update_names = function (fontnames, force, dry_run)
+update_names = function (currentnames, force, dry_run)
- if config.luaotfload.update_live == false then
- report("info", 1, "db",
- "Skipping database update")
+ local targetnames
+
+ if luaotfloadconfig.update_live == false then
+ report ("info", 2, "db",
+ "Skipping database update")
--- skip all db updates
- return fontnames or names.data
+ return currentnames or name_index
end
- local starttime = os.gettimeofday()
- local n_scanned, n_new = 0, 0
+ local starttime = osgettimeofday ()
+ local n_rawnames, n_newnames = 0, 0
--[[
The main function, scans everything
- - “newfontnames” is the final table to return
+ - “targetnames” is the final table to return
- force is whether we rebuild it from scratch or not
]]
report("both", 1, "db", "Updating the font names database"
.. (force and " forcefully" or ""))
- if force then
- fontnames = fontnames_init (get_font_filter ())
+ --- pass 1 get raw data: read font files (normal case) or reuse
+ --- information present in index
+
+ if luaotfloadconfig.skip_read == true then
+ --- the difference to a “dry run” is that we don’t search
+ --- for font files entirely. we also ignore the “force”
+ --- parameter since it concerns only the font files.
+ report ("info", 2, "db",
+ "Ignoring font files, reusing old data.")
+ currentnames = load_names (false)
+ targetnames = currentnames
else
- if not fontnames then
- fontnames = load_names (dry_run)
- end
- if fontnames.version ~= names.version then
- report ("both", 1, "db", "No font names database or old "
- .. "one found; generating new one")
- fontnames = fontnames_init (get_font_filter ())
+ if force then
+ currentnames = initialize_namedata (get_font_filter ())
+ else
+ if not currentnames then
+ currentnames = load_names (dry_run)
+ end
+ if currentnames.meta.version ~= names.version then
+ report ("both", 1, "db", "No font names database or old "
+ .. "one found; generating new one")
+ currentnames = initialize_namedata (get_font_filter ())
+ end
end
- end
- local newfontnames = fontnames_init (get_font_filter ())
- read_blacklist ()
- local scanned, new
- scanned, new = scan_texmf_fonts (fontnames, newfontnames, dry_run)
- n_scanned = n_scanned + scanned
- n_new = n_new + new
+ targetnames = initialize_namedata (get_font_filter ())
+
+ read_blacklist ()
- scanned, new = scan_os_fonts (fontnames, newfontnames, dry_run)
- n_scanned = n_scanned + scanned
- n_new = n_new + new
+ local n_raw, n_new= retrieve_namedata (currentnames,
+ targetnames,
+ dry_run,
+ n_rawnames,
+ n_newnames)
+ report ("info", 3, "db",
+ "Scanned %d font files; %d new entries.",
+ n_rawnames, n_newnames)
+ end
+
+ --- pass 2 (optional): collect some stats about the raw font info
+ if luaotfloadconfig.statistics == true then
+ targetnames.meta.statistics = collect_statistics
+ (targetnames.mappings)
+ end
--- we always generate the file lookup tables because
--- non-texmf entries are redirected there and the mapping
--- needs to be 100% consistent
- newfontnames = gen_fast_lookups(newfontnames)
-
- --- stats:
- --- before rewrite | after rewrite
- --- partial: 804 ms | 701 ms
- --- forced: 45384 ms | 44714 ms
- report("info", 3, "db",
- "Scanned %d font files; %d new entries.", n_scanned, n_new)
- report("info", 3, "db",
- "Rebuilt in %0.f ms", 1000*(os.gettimeofday()-starttime))
- names.data = newfontnames
+
+ --- pass 3: build filename table
+ targetnames.files = generate_filedata (targetnames.mappings)
+
+ --- pass 4: build family lookup table
+ targetnames.families = collect_families (targetnames.mappings)
+
+ --- pass 5: order design size tables
+ targetnames.families = order_design_sizes (targetnames.families)
+
+
+ report ("info", 3, "db",
+ "Rebuilt in %0.f ms.",
+ 1000 * (osgettimeofday () - starttime))
+ name_index = targetnames
if dry_run ~= true then
@@ -2335,11 +3167,11 @@ update_names = function (fontnames, force, dry_run)
if success then
logs.names_report ("info", 2, "cache",
"Lookup cache emptied")
- return newfontnames
+ return targetnames
end
end
end
- return newfontnames
+ return targetnames
end
--- unit -> bool
@@ -2371,22 +3203,43 @@ end
--- save_names() is usually called without the argument
--- dbobj? -> bool
-save_names = function (fontnames)
- if not fontnames then fontnames = names.data end
+save_names = function (currentnames)
+ if not currentnames then
+ currentnames = name_index
+ end
local path = names.path.index
local luaname, lucname = path.lua, path.luc
if fileiswritable (luaname) and fileiswritable (lucname) then
- tabletofile (luaname, fontnames, true)
osremove (lucname)
- caches.compile (fontnames, luaname, lucname)
- if lfsisfile (luaname) and lfsisfile (lucname) then
- report ("info", 1, "db", "Font index saved")
+ local gzname = luaname .. ".gz"
+ if luaotfloadconfig.compress then
+ local serialized = tableserialize (currentnames, true)
+ gzipsave (gzname, serialized)
+ caches.compile (currentnames, "", lucname)
+ else
+ tabletofile (luaname, currentnames, true)
+ caches.compile (currentnames, luaname, lucname)
+ end
+ report ("info", 1, "db", "Font index saved at ...")
+ local success = false
+ if lfsisfile (luaname) then
report ("info", 3, "db", "Text: " .. luaname)
+ success = true
+ end
+ if lfsisfile (gzname) then
+ report ("info", 3, "db", "Gzip: " .. gzname)
+ success = true
+ end
+ if lfsisfile (lucname) then
report ("info", 3, "db", "Byte: " .. lucname)
+ success = true
+ end
+ if success then
return true
+ else
+ report ("info", 0, "db", "Could not compile font index")
+ return false
end
- report ("info", 0, "db", "Could not compile font index")
- return false
end
report ("info", 0, "db", "Index file not writable")
if not fileiswritable (luaname) then
@@ -2477,7 +3330,7 @@ end
local getwritablecachepath = function ( )
--- fonts.handlers.otf doesn’t exist outside a Luatex run,
--- so we have to improvise
- local writable = getwritablepath (config.luaotfload.cache_dir)
+ local writable = getwritablepath (luaotfloadconfig.cache_dir)
if writable then
return writable
end
@@ -2485,7 +3338,7 @@ end
local getreadablecachepaths = function ( )
local readables = caches.getreadablepaths
- (config.luaotfload.cache_dir)
+ (luaotfloadconfig.cache_dir)
local result = { }
if readables then
for i=1, #readables do
@@ -2555,12 +3408,13 @@ names.set_font_filter = set_font_filter
names.flush_lookup_cache = flush_lookup_cache
names.save_lookups = save_lookups
names.load = load_names
+names.data = function () return name_index end
names.save = save_names
names.update = update_names
names.crude_file_lookup = crude_file_lookup
names.crude_file_lookup_verbose = crude_file_lookup_verbose
names.read_blacklist = read_blacklist
-names.sanitize_string = sanitize_string
+names.sanitize_fontname = sanitize_fontname
names.getfilename = resolve_fullpath
--- font cache
@@ -2569,13 +3423,13 @@ names.erase_cache = erase_cache
names.show_cache = show_cache
--- replace the resolver from luatex-fonts
-if config.luaotfload.resolver == "cached" then
+if luaotfloadconfig.resolver == "cached" then
report("both", 2, "cache", "caching of name: lookups active")
names.resolve = resolve_cached
names.resolvespec = resolve_cached
else
- names.resolve = resolve
- names.resolvespec = resolve
+ names.resolvespec = resolve_name
+ names.resolve_name = resolve_name
end
names.find_closest = find_closest