summaryrefslogtreecommitdiff
path: root/luaotfload-database.lua
diff options
context:
space:
mode:
Diffstat (limited to 'luaotfload-database.lua')
-rw-r--r--luaotfload-database.lua3445
1 files changed, 0 insertions, 3445 deletions
diff --git a/luaotfload-database.lua b/luaotfload-database.lua
deleted file mode 100644
index 4b2d201..0000000
--- a/luaotfload-database.lua
+++ /dev/null
@@ -1,3445 +0,0 @@
-if not modules then modules = { } end modules ['luaotfload-database'] = {
- version = "2.5",
- comment = "companion to luaotfload-main.lua",
- author = "Khaled Hosny, Elie Roux, Philipp Gesang",
- copyright = "Luaotfload Development Team",
- license = "GNU GPL v2.0"
-}
-
---[[doc--
-
- Some statistics:
-
- a) TL 2012, mkluatexfontdb --force
- b) v2.4, luaotfload-tool --update --force
- c) v2.4, luaotfload-tool --update --force --formats=+afm,pfa,pfb
- d) Context, mtxrun --script fonts --reload --force
-
- (Keep in mind that Context does index fewer fonts since it
- considers only the contents of the minimals tree, not the
- tex live one!)
-
- time (m:s) peak VmSize (kB)
- a 1:19 386 018
- b 0:37 715 797
- c 2:27 1 017 674
- d 0:44 1 082 313
-
- Most of the increase in memory consumption from version 1.x to 2.2+
- can be attributed to the move from single-pass to a multi-pass
- approach to building the index: Information is first gathered from
- all reachable fonts and only afterwards processed, classified and
- discarded. Also, there is a good deal of additional stuff kept in
- the database now: two extra tables for file names and font families
- have been added, making font lookups more efficient while improving
- maintainability of the code.
-
---doc]]--
-
-local lpeg = require "lpeg"
-local P, Cc, lpegmatch = lpeg.P, lpeg.Cc, lpeg.match
-
-local parsers = luaotfload.parsers
-local read_fonts_conf = parsers.read_fonts_conf
-local stripslashes = parsers.stripslashes
-local splitcomma = parsers.splitcomma
-
-local log = luaotfload.log
-local report = log.report
-local report_status = log.names_status
-local report_status_start = log.names_status_start
-local report_status_stop = log.names_status_stop
-
-
---- Luatex builtins
-local load = load
-local next = next
-local require = require
-local tonumber = tonumber
-local unpack = table.unpack
-
-local fontloaderinfo = fontloader.info
-local fontloaderclose = fontloader.close
-local fontloaderopen = fontloader.open
------ fontloaderto_table = fontloader.to_table
-local gzipopen = gzip.open
-local iolines = io.lines
-local ioopen = io.open
-local iopopen = io.popen
-local kpseexpand_path = kpse.expand_path
-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 osgetenv = os.getenv
-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 utf8gsub = unicode.utf8.gsub
-local utf8lower = unicode.utf8.lower
-local utf8len = unicode.utf8.len
-local zlibcompress = zlib.compress
-
---- these come from Lualibs/Context
-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 tablecontains = table.contains
-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 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
-
---- this option allows for disabling updates
---- during a TeX run
-luaotfloadconfig.update_live = luaotfloadconfig.update_live ~= false
-luaotfloadconfig.compress = luaotfloadconfig.compress ~= false
-
-local names = fonts.names
-local name_index = nil --> upvalue for names.data
-local lookup_cache = nil --> for names.lookups
-names.version = 2.5
-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)
-local make_luanames = function (path)
- return filereplacesuffix(path, "lua"),
- filereplacesuffix(path, "luc")
-end
-
---- The “termwidth” value is only considered when printing
---- short status messages, e.g. when building the database
---- online.
-if not luaotfloadconfig.termwidth then
- local tw = 79
- if not ( os.type == "windows" --- Assume broken terminal.
- or osgetenv "TERM" == "dumb")
- then
- local p = iopopen "tput cols"
- if p then
- result = tonumber (p:read "*all")
- p:close ()
- if result then
- tw = result
- else
- report ("log", 2, "db", "tput returned non-number.")
- end
- else
- report ("log", 2, "db", "Shell escape disabled or tput executable missing.")
- report ("log", 2, "db", "Assuming 79 cols terminal width.")
- end
- end
- luaotfloadconfig.termwidth = tw
-end
-
-local format_precedence = {
- "otf", "ttc", "ttf",
- "dfont", "afm", "pfb",
- "pfa",
-}
-
-local location_precedence = {
- "local", "system", "texmf",
-}
-
-local set_location_precedence = function (precedence)
- location_precedence = precedence
-end
-
---[[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
- since most checks and directory creations are already done. It
- uses TEXMFCACHE or TEXMFVAR as starting points.
-
- There is one quirk, though: ``getwritablepath()`` will always
- assume that files in subdirectories of the cache tree are writable.
- It gives no feedback at all if it fails to open a file in write
- mode. This may cause trouble when the index or lookup cache were
- created by different user.
---doc]]--
-
-if not runasscript then
- local globals = names.path.globals
- local names_dir = globals.names_dir
-
- prefix = getwritablepath (names_dir, "")
- if not prefix then
- luaotfload.error
- ("Impossible to find a suitable writeable cache...")
- else
- prefix = lpegmatch (stripslashes, prefix)
- report ("log", 0, "db",
- "Root cache directory is %s.", prefix)
- end
-
- globals.prefix = prefix
- local lookup_path = names.path.lookups
- local index = names.path.index
- local lookups_file = filejoin (prefix, globals.lookups_file)
- local index_file = filejoin (prefix, globals.index_file)
- lookup_path.lua, lookup_path.luc = make_luanames (lookups_file)
- index.lua, index.luc = make_luanames (index_file)
-else --- running as script, inject some dummies
- caches = { }
- local dummy_function = function () end
- log = { report = dummy_function,
- report_status = dummy_function,
- report_status_start = dummy_function,
- report_status_stop = dummy_function, }
-end
-
-
---[[doc--
-Auxiliary functions
---doc]]--
-
---- fontnames contain all kinds of garbage; as a precaution we
---- lowercase and strip them of non alphanumerical characters
-
---- string -> string
-
-local invalidchars = "[^%a%d]"
-
-local sanitize_fontname = function (str)
- if str ~= nil then
- 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
- return acc
- end
-
- local pwd = lfscurrentdir ()
- local dir = dirs[#dirs]
- dirs[#dirs] = nil
-
- if lfschdir (dir) then
- lfschdir (pwd)
-
- local newfiles = { }
- for ent in lfsdir (dir) do
- if ent ~= "." and ent ~= ".." then
- local fullpath = dir .. "/" .. ent
- if filter (fullpath) == true then
- if lfsisdir (fullpath) then
- dirs[#dirs+1] = fullpath
- elseif lfsisfile (fullpath) then
- newfiles[#newfiles+1] = fullpath
- end
- end
- end
- end
- return find_files_indeed (tableappend (acc, newfiles),
- dirs, filter)
- end
- --- could not cd into, so we skip it
- return find_files_indeed (acc, dirs, filter)
-end
-
-local dummyfilter = function () return true end
-
---- the optional filter function receives the full path of a file
---- system entity. a filter applies if the first argument it returns is
---- true.
-
---- string -> function? -> string list
-local find_files = function (root, filter)
- if lfsisdir (root) then
- return find_files_indeed ({}, { root }, filter or dummyfilter)
- end
-end
-
-
---[[doc--
-This is a sketch of the luaotfload db:
-
- type dbobj = {
- 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 : {
- 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 = {
- barename : string;
- familyname : string;
- filename : string;
- fontname : string; // <- metadata
- fullname : string; // <- metadata
- sanitized : {
- family : string;
- fontstyle_name : string; // <- new in 2.4
- fontname : string; // <- metadata
- fullname : string; // <- namedata.names
- metafamily : string;
- pfullname : string;
- prefmodifiers : string;
- psname : string;
- subfamily : string;
- };
- size : int list;
- slant : int;
- subfont : int;
- location : local | system | texmf;
- weight : int;
- width : int;
- units_per_em : int; // mainly 1000, but also 2048 or 256
- }
- and filestatus = (string, // fullname
- { index : int list; // pointer into mappings
- timestamp : int; }) dict
-
-beware that this is a reconstruction and may be incomplete.
-
-mtx-fonts has in names.tma:
-
- type names = {
- cache_uuid : uuid;
- cache_version : float;
- datastate : uuid list;
- fallbacks : (filetype, (basename, int) hash) hash;
- families : (basename, int list) hash;
- files : (filename, fullname) hash;
- indices : (fullname, int) hash;
- mappings : (filetype, (basename, int) hash) hash;
- names : ? (empty hash) ?;
- rejected : (basename, int) hash;
- specifications: fontentry list;
- }
- and fontentry = {
- designsize : int;
- familyname : string;
- filename : string;
- fontname : string;
- format : string;
- fullname : string;
- maxsize : int;
- minsize : int;
- modification : int;
- rawname : string;
- style : string;
- subfamily : string;
- variant : string;
- weight : string;
- width : string;
- }
-
---doc]]--
-
-local initialize_namedata = function (formats) --- returns dbobj
- return {
- --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
-
---[[doc--
-
- Since Luaotfload does not depend on the lualibs anymore we
- have to put our own small wrappers for the gzip library in
- place.
-
- load_gzipped -- Read and decompress and entire gzipped file.
- Returns the uncompressed content as a string.
-
---doc]]--
-
-local load_gzipped = function (filename)
- local gh = gzipopen (filename,"rb")
- if gh then
- local data = gh:read "*all"
- gh:close ()
- return data
- end
-end
-
---[[doc--
-
- save_gzipped -- Compress and write a string to file. The return
- value is the number of bytes written. Zlib parameters are: best
- compression and default strategy.
-
---doc]]--
-
-local save_gzipped = function (filename, data)
- local gh = gzipopen (filename, "wb9")
- if gh then
- gh:write (data)
- local bytes = gh:seek ()
- gh:close ()
- return bytes
- end
-end
-
---- When loading a lua file we try its binary complement first, which
---- is assumed to be located at an identical path, carrying the suffix
---- .luc.
-
---- string -> (string * table)
-local load_lua_file = function (path)
- local foundname = filereplacesuffix (path, "luc")
- local code = nil
-
- local fh = ioopen (foundname, "rb") -- try bin first
- if fh then
- local chunk = fh:read"*all"
- fh:close()
- code = load (chunk, "b")
- end
-
- if not code then --- fall back to text file
- foundname = filereplacesuffix (path, "lua")
- fh = ioopen(foundname, "rb")
- if fh then
- local chunk = fh:read"*all"
- fh:close()
- code = load (chunk, "t")
- end
- end
-
- if not code then --- probe gzipped file
- foundname = filereplacesuffix (path, "lua.gz")
- local chunk = load_gzipped (foundname)
- if chunk then
- code = load (chunk, "t")
- end
- end
-
- if not code then return nil, nil end
- return foundname, code ()
-end
-
---- define locals in scope
-local crude_file_lookup
-local crude_file_lookup_verbose
-local find_closest
-local flush_lookup_cache
-local ot_fullinfo
-local t1_fullinfo
-local load_names
-local load_lookups
-local read_blacklist
-local reload_db
-local resolve_name
-local resolve_cached
-local resolve_fullpath
-local save_names
-local save_lookups
-local update_names
-local get_font_filter
-local set_font_filter
-
---- state of the database
-local fonts_reloaded = false
-local fonts_read = 0
-
---- limit output when approximate font matching (luaotfload-tool -F)
-local fuzzy_limit = 1 --- display closest only
-
---- bool? -> dbobj
-load_names = function (dry_run)
- local starttime = osgettimeofday ()
- local foundname, data = load_lua_file (names.path.index.lua)
-
- if data then
- report ("both", 2, "db",
- "Font names database loaded", "%s", foundname)
- report ("info", 3, "db", "Loading took %0.f ms.",
- 1000 * (osgettimeofday () - starttime))
-
- local db_version, nms_version
- if data.meta then
- db_version = data.meta.version
- else
- --- Compatibility branch; the version info used to be
- --- stored in the table root which is why updating from
- --- an earlier index version broke.
- db_version = data.version or -42 --- invalid
- end
- nms_version = names.version
- if db_version ~= nms_version then
- report ("both", 0, "db",
- [[Version mismatch; expected %4.3f, got %4.3f.]],
- nms_version, db_version)
- if not fonts_reloaded then
- report ("both", 0, "db", [[Force rebuild.]])
- data = update_names ({ }, true, false)
- if not data then
- report ("both", 0, "db",
- "Database creation unsuccessful.")
- end
- end
- end
- else
- report ("both", 0, "db",
- [[Font names database not found, generating new one.]])
- report ("both", 0, "db",
- [[This can take several minutes; please be patient.]])
- data = update_names (initialize_namedata (get_font_filter ()),
- nil, dry_run)
- if not data then
- report ("both", 0, "db", "Database creation unsuccessful.")
- end
- end
- return data
-end
-
---- unit -> unit
-load_lookups = function ( )
- local foundname, data = load_lua_file(names.path.lookups.lua)
- if data then
- report("both", 3, "cache",
- "Lookup cache loaded from %s.", foundname)
- else
- report("both", 1, "cache",
- "No lookup cache, creating empty.")
- data = { }
- end
- lookup_cache = data
-end
-
-local regular_synonym = {
- book = "r",
- normal = "r",
- plain = "r",
- regular = "r",
- roman = "r",
-}
-
-local italic_synonym = {
- oblique = true,
- slanted = true,
- italic = true,
-}
-
-local style_category = {
- regular = "r",
- bold = "b",
- bolditalic = "bi",
- italic = "i",
- r = "regular",
- b = "bold",
- bi = "bolditalic",
- i = "italic",
-}
-
-local type1_formats = { "tfm", "ofm", }
-
-local dummy_findfile = resolvers.findfile -- from basics-gen
-
---- filemap -> string -> string -> (string | bool)
-local verbose_lookup = function (data, kind, filename)
- local found = data[kind][filename]
- if found ~= nil then
- found = data.full[found]
- if found == nil then --> texmf
- report("info", 0, "db",
- "Crude file lookup: req=%s; hit=%s => kpse.",
- filename, kind)
- found = dummy_findfile(filename)
- else
- report("info", 0, "db",
- "Crude file lookup: req=%s; hit=%s; ret=%s.",
- filename, kind, found)
- end
- return found
- end
- return false
-end
-
---- string -> (string * string * bool)
-crude_file_lookup_verbose = function (filename)
- 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(files, "bare", filename)
- if found then
- return found, nil, true
- end
- found = verbose_lookup(files, "base", filename)
- if found then
- return found, nil, true
- end
-
- --- ofm and tfm, returns pair
- for i=1, #type1_formats do
- local format = type1_formats[i]
- if resolvers.findfile(filename, format) then
- return file.addsuffix(filename, format), format, true
- end
- end
- 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)
- local found = lookup_filename (filename)
-
- if not found then
- found = dummy_findfile(filename)
- end
-
- if found then
- return found, nil, true
- end
-
- for i=1, #type1_formats do
- local format = type1_formats[i]
- if resolvers.findfile(filename, format) then
- return file.addsuffix(filename, format), format, true
- end
- end
-
- return filename, nil, false
-end
-
---[[doc--
-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 (index)
- local entry = name_index.mappings [index]
- if not entry then
- return false
- end
- local basename = entry.basename
- if entry.location == "texmf" then
- if kpselookup(basename) then
- return true, basename, entry.subfont
- end
- else --- system, local
- local fullname = name_index.files.full [index]
- if lfsisfile (fullname) then
- return true, basename, entry.subfont
- end
- end
- return false
-end
-
---[[doc--
-We need to verify if the result of a cached lookup actually exists in
-the texmf or filesystem. Again, due to the schizoprenic nature of the
-font managment we have to check both the system path and the texmf.
---doc]]--
-
-local verify_font_file = function (basename)
- local path = resolve_fullpath (basename)
- if path and lfsisfile(path) then
- return true
- end
- if kpsefind_file(basename) then
- return true
- end
- return false
-end
-
---[[doc--
-Lookups can be quite costly, more so the less specific they are.
-Even if we find a matching font eventually, the next time the
-user compiles Eir document E will have to stand through the delay
-again.
-Thus, some caching of results -- even between runs -- is in order.
-We’ll just store successful name: lookups in a separate cache file.
-
-type lookup_cache = (string, (string * num)) dict
-
-The spec is expected to be modified in place (ugh), so we’ll have to
-catalogue what fields actually influence its behavior.
-
-Idk what the “spec” resolver is for.
-
- lookup inspects modifies
- ---------- ----------------- ---------------------------
- file: name forced, name
- name:[*] name, style, sub, resolved, sub, name, forced
- optsize, size
- spec: name, sub resolved, sub, name, forced
-
-[*] name: contains both the name resolver from luatex-fonts and
- resolve_name() below
-
-From my reading of font-def.lua, what a resolver does is
-basically rewrite the “name” field of the specification record
-with the resolution.
-Also, the fields “resolved”, “sub”, “force” etc. influence the outcome.
-
---doc]]--
-
-local concat_char = "#"
-local hash_fields = {
- --- order is important
- "specification", "style", "sub", "optsize", "size",
-}
-local n_hash_fields = #hash_fields
-
---- spec -> string
-local hash_request = function (specification)
- local key = { } --- segments of the hash
- for i=1, n_hash_fields do
- local field = specification[hash_fields[i]]
- if field then
- key[#key+1] = field
- end
- end
- return tableconcat(key, concat_char)
-end
-
---- 'a -> 'a -> table -> (string * int|boolean * boolean)
-resolve_cached = function (specification)
- if not lookup_cache then load_lookups () end
- local request = hash_request(specification)
- report("both", 4, "cache", "Looking for %q in cache ...",
- request)
-
- local found = lookup_cache [request]
-
- --- case 1) cache positive ----------------------------------------
- if found then --- replay fields from cache hit
- report("info", 4, "cache", "Found!")
- local basename = found[1]
- --- check the presence of the file in case it’s been removed
- local success = verify_font_file (basename)
- if success == true then
- return basename, found[2], true
- end
- report("both", 4, "cache", "Cached file not found; resolving again.")
- else
- report("both", 4, "cache", "Not cached; resolving.")
- end
-
- --- case 2) cache negative ----------------------------------------
- --- first we resolve normally ...
- local filename, subfont = resolve_name (specification)
- if not filename then
- return nil, nil
- end
- --- ... then we add the fields to the cache ... ...
- local entry = { filename, subfont }
- report("both", 4, "cache", "New entry: %s.", request)
- lookup_cache [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
- report("both", 0, "cache", "Error writing cache.")
- end
- return filename, subfont
-end
-
---- this used to be inlined; with the lookup cache we don’t
---- have to be parsimonious wrt function calls anymore
---- “found” is the match accumulator
-local add_to_match = function (found, size, face)
-
- local continue = true
-
- local optsize = face.size
-
- if optsize and next (optsize) then
- local dsnsize, maxsize, minsize
- dsnsize = optsize[1]
- maxsize = optsize[2]
- minsize = optsize[3]
-
- if size ~= nil
- and (dsnsize == size or (size > minsize and size <= maxsize))
- then
- found[1] = face
- continue = false ---> break
- else
- found[#found+1] = face
- end
- else
- found[1] = face
- continue = false ---> break
- end
-
- 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--
-
- choose_size -- Pick a font face of appropriate size from the list
- of family members with matching style. There are three categories:
-
- 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.
-
- 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.
-
- 4. default matches: if no design size or a design size of zero
- is requested, the face with the default size is returned.
-
---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
-::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
-
---[[doc--
-
- resolve_familyname -- Query the families table for an entry
- matching the specification.
- The parameters “name” and “style” are pre-sanitized.
-
---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 resolve_fontname = function (specification, name, style)
- local mappings = name_index.mappings
- local fallback = nil
- local lastresort = nil
- style = style_category [style]
- for i = 1, #mappings do
- local face = mappings [i]
- local prefmodifiers = face.prefmodifiers
- local subfamily = face.subfamily
- if face.fontname == name
- or face.splainname == name
- or face.fullname == name
- or face.psname == name
- then
- return face.basename, face.subfont
- elseif face.familyname == name then
- if prefmodifiers == style
- or subfamily == style
- then
- fallback = face
- elseif regular_synonym [prefmodifiers]
- or regular_synonym [subfamily]
- then
- lastresort = face
- end
- elseif face.metafamily == name
- and (regular_synonym [prefmodifiers]
- or regular_synonym [subfamily])
- then
- lastresort = face
- end
- end
- if fallback then
- return fallback.basename, fallback.subfont
- end
- if lastresort then
- return lastresort.basename, lastresort.subfont
- end
- return nil, nil
-end
-
---[[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
-
- resolved, subfont = resolve_familyname (specification,
- name,
- style,
- askedsize)
- if not resolved then
- resolved, subfont = resolve_fontname (specification,
- name,
- style)
- end
- if not resolved then
- resolved = specification.name, false
- end
-
- if not resolved then
- if not fonts_reloaded then
- return reload_db ("Font not found.",
- resolve_name,
- specification)
- end
- end
- return resolved, subfont
-end
-
-resolve_fullpath = function (fontname, ext) --- getfilename()
- 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 idx
- if basenames ~= nil then
- idx = basenames [fontname]
- end
- if ext then
- local barenames = baredata [location] [ext]
- if not idx and barenames ~= nil then
- idx = barenames [fontname]
- end
- end
- if idx then
- return files.full [idx]
- end
- end
- return ""
-end
-
---- when reload is triggered we update the database
---- and then re-run the caller with the arg list
-
---- string -> ('a -> 'a) -> 'a list -> 'a
-reload_db = function (why, caller, ...)
- 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)
- namedata = update_names (namedata, false, false)
-
- if namedata then
- fonts_reloaded = true
- name_index = namedata
- return caller (...)
- end
-
- report ("both", 0, "db", "Database update unsuccessful.")
-end
-
---- string -> string -> int
-local iterative_levenshtein = function (s1, s2)
-
- local costs = { }
- local len1, len2 = #s1, #s2
-
- for i = 0, len1 do
- local last = i
- for j = 0, len2 do
- if i == 0 then
- costs[j] = j
- else
- if j > 0 then
- local current = costs[j-1]
- if stringsub(s1, i, i) ~= stringsub(s2, j, j) then
- current = mathmin(current, last, costs[j]) + 1
- end
- costs[j-1] = last
- last = current
- end
- end
- end
- if i > 0 then costs[len2] = last end
- end
-
- return costs[len2]--- lower right has the distance
-end
-
---- string -> int -> bool
-find_closest = function (name, limit)
- local name = sanitize_fontname (name)
- limit = limit or fuzzy_limit
-
- 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 = name_index.mappings
- local n_fonts = #mappings
-
- for n = 1, n_fonts do
- local current = mappings[n]
- --[[
- This is simplistic but surpisingly fast.
- Matching is performed against the “fullname” field
- of a db record in preprocessed form. We then store the
- raw “fullname” at its edit distance.
- We should probably do some weighting over all the
- font name categories as well as whatever agrep
- does.
- --]]
- local fullname = current.plainname
- local sfullname = current.fullname
- 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
- end
-
- --- print the matches according to their distance
- local n_distances = #distances
- if n_distances > 0 then --- got some data
- tablesort(distances)
- limit = mathmin(n_distances, limit)
- report(false, 1, "query",
- "Displaying %d distance levels.", limit)
-
- for i = 1, limit do
- local dist = distances[i]
- local namelst = by_distance[dist]
- report(false, 0, "query",
- "Distance from \"%s\": %s\n "
- .. tableconcat (namelst, "\n "),
- name, dist)
- end
-
- return true
- end
- return false
-end --- find_closest()
-
---[[doc--
-
- load_font_file -- Safely open a font file. See
- <http://www.ntg.nl/pipermail/ntg-context/2013/075885.html>
- regarding the omission of ``fontloader.close()``.
-
- TODO -- check if fontloader.info() is ready for prime in 0.78+
- -- fields /tables needed:
- -- names
- -- postscriptname
- -- validation_state
- -- ..
-
---doc]]--
-
-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
- return rawfont
-end
-
---- rawdata -> (int * int * int | bool)
-
-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 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 (metadata)
- local names = metadata.names
- local english_names
-
- if names then
- --inspect(names)
- for _, raw_namedata in next, names do
- if raw_namedata.lang == "English (US)" then
- return raw_namedata.names
- end
- end
- end
-
- -- no (English) names table, probably a broken font
- report("both", 3, "db",
- "%s: missing or broken English names table.", basename)
- return { fontname = metadata.fontname,
- fullname = metadata.fullname, }
-end
-
---[[--
- In case of broken PS names we set some dummies. However, we cannot
- directly modify the font data as returned by fontloader.open() because
- it is a userdata object.
-
- For this reason we copy what is necessary whilst keeping the table
- structure the same as in the tfmdata.
---]]--
-local get_raw_info = function (metadata, basename)
- local fullname
- local fontname
- local psname
-
- local validation_state = metadata.validation_state
- if validation_state
- and tablecontains (validation_state, "bad_ps_fontname")
- then
- --- Broken names table, e.g. avkv.ttf with UTF-16 strings;
- --- we put some dummies in place like the fontloader
- --- (font-otf.lua) does.
- report("both", 3, "db",
- "%s has invalid postscript font names, using dummies.",
- basename)
- fontname = "bad-fontname-" .. basename
- fullname = "bad-fullname-" .. basename
- else
- fontname = metadata.fontname
- fullname = metadata.fullname
- end
-
- return {
- familyname = metadata.familyname,
- fontname = fontname,
- fontstyle_name = metadata.fontstyle_name,
- fullname = fullname,
- italicangle = metadata.italicangle,
- names = metadata.names,
- pfminfo = metadata.pfminfo,
- units_per_em = metadata.units_per_em,
- version = metadata.version,
- design_size = metadata.design_size,
- design_range_top = metadata.design_range_top,
- design_range_bottom = metadata.design_range_bottom,
- }
-end
-
-local organize_namedata = function (rawinfo,
- english_names,
- basename,
- info)
- local default_name = english_names.compatfull
- or english_names.fullname
- or english_names.postscriptname
- or rawinfo.fullname
- or rawinfo.fontname
- or info.fullname
- or info.fontname
- local default_family = english_names.preffamily
- or english_names.family
- or rawinfo.familyname
- or info.familyname
--- local default_modifier = english_names.prefmodifiers
--- or english_names.subfamily
- local fontnames = {
- --- see
- --- https://developer.apple.com/fonts/TTRefMan/RM06/Chap6name.html
- --- 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 = --[[ 18 ]] english_names.compatfull
- or --[[ 4 ]] english_names.fullname
- or default_name,
- --- 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 = --[[ 16 ]] english_names.preffamilyname,
- family = --[[ 1 ]] english_names.family or default_family,
- prefmodifiers = --[[ 17 ]] english_names.prefmodifiers,
- subfamily = --[[ 2 ]] english_names.subfamily,
- psname = --[[ 6 ]] english_names.postscriptname,
- },
-
- metadata = {
- fullname = rawinfo.fullname,
- fontname = rawinfo.fontname,
- familyname = rawinfo.familyname,
- },
-
- info = {
- fullname = info.fullname,
- familyname = info.familyname,
- fontname = info.fontname,
- },
- }
-
- -- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size
- if rawinfo.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, rawinfo.fontstyle_name do
- if name.lang == 1033 then --- I hate magic numbers
- fontnames.fontstyle_name = name.name
- end
- end
- end
-
- return {
- sanitized = sanitize_fontnames (fontnames),
- fontname = rawinfo.fontname,
- fullname = rawinfo.fullname,
- familyname = rawinfo.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
-
- return {
- --- see http://www.microsoft.com/typography/OTSPEC/features_pt.htm#size
- size = get_size_info (metadata),
- weight = pfminfo.weight or 400,
- split = split_fontname (fontname),
- width = pfminfo.width,
- italicangle = metadata.italicangle,
- --- this is for querying, see www.ntg.nl/maps/40/07.pdf for details
- units_per_em = metadata.units_per_em,
- version = metadata.version,
- }
-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]]--
-
---- string -> int -> bool -> string -> fontentry
-
-ot_fullinfo = function (filename,
- subfont,
- location,
- basename,
- format,
- info)
-
- local metadata = load_font_file (filename, subfont)
- if not metadata then
- return nil
- end
-
- local rawinfo = get_raw_info (metadata, basename)
- --- Closing the file manually is a tad faster and more memory
- --- efficient than having it closed by the gc
- fontloaderclose (metadata)
-
- local english_names = get_english_names (rawinfo)
- local namedata = organize_namedata (rawinfo,
- english_names,
- basename,
- info)
- local style = organize_styledata (namedata.fontname,
- rawinfo,
- english_names,
- info)
-
- local res = {
- file = { base = basename,
- full = filename,
- subfont = subfont,
- location = location or "system" },
- format = format,
- names = namedata,
- style = style,
- version = rawinfo.version,
- }
- return res
-end
-
---[[doc--
-
- Type1 font inspector. In comparison with OTF, PFB’s contain a good
- deal less name fields which makes it tricky in some parts to find a
- meaningful representation for the database.
-
- Good read: http://www.adobe.com/devnet/font/pdfs/5004.AFM_Spec.pdf
-
---doc]]--
-
---- string -> int -> bool -> string -> fontentry
-
-t1_fullinfo = function (filename, _subfont, location, basename, format)
- local sanitized
- local metadata = load_font_file (filename)
- local fontname = metadata.fontname
- local fullname = metadata.fullname
- local familyname = metadata.familyname
- local italicangle = metadata.italicangle
- local splitstyle = split_fontname (fontname)
- local style = ""
- local weight
-
- sanitized = sanitize_fontnames ({
- fontname = fontname,
- psname = fullname,
- pfullname = fullname,
- metafamily = family,
- familyname = familyname,
- weight = metadata.weight, --- string identifier
- prefmodifiers = style,
- })
-
- weight = sanitized.weight
-
- if weight == "bold" then
- style = weight
- end
-
- if italicangle ~= 0 then
- style = style .. "italic"
- end
-
- return {
- basename = basename,
- fullpath = filename,
- subfont = false,
- location = location or "system",
- format = format,
- fullname = sanitized.fullname,
- fontname = sanitized.fontname,
- familyname = sanitized.familyname,
- plainname = fullname,
- splainname = sanitized.fullname,
- psname = sanitized.fontname,
- version = metadata.version,
- size = false,
- splitstyle = splitstyle,
- fontstyle_name = style ~= "" and style or weight,
- weight = metadata.pfminfo.weight or 400,
- italicangle = italicangle,
- }
-end
-
-local loaders = {
- dfont = ot_fullinfo,
- otf = ot_fullinfo,
- ttc = ot_fullinfo,
- ttf = ot_fullinfo,
-
- pfb = t1_fullinfo,
- pfa = t1_fullinfo,
-}
-
---- 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 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 currentmappings = currentnames.mappings
- local currentstatus = currentnames.status
- local currententrystatus = currentstatus [fullname]
-
- local basename = filebasename (fullname)
- local barename = filenameonly (fullname)
- local entryname = fullname
-
- if location == "texmf" then
- entryname = basename
- end
-
- --- 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
-
- --- 2) skip if known with same timestamp
-
- if not compare_timestamps (fullname,
- currentstatus,
- currententrystatus,
- currentmappings,
- targetstatus,
- targetentrystatus,
- targetmappings)
- then
- return false
- end
-
- --- 3) new font; choose a loader, abort if unknown
-
- local format = stringlower (filesuffix (basename))
- local loader = loaders [format] --- ot_fullinfo, t1_fullinfo
-
- if not loader then
- report ("both", 0, "db",
- "Unknown format: %q, skipping.", format)
- return false
- end
-
- --- 4) get basic info, abort if fontloader can’t read it
-
- local info = fontloaderinfo (fullname)
-
- 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
-
- return success
- end
-
- return insert_fullinfo (fullname, basename, false,
- loader, format, location,
- targetmappings, targetentrystatus,
- info)
-end
-
-local path_normalize
-do
- --- os.type and os.name are constants so we
- --- choose a normalization function in advance
- --- instead of testing with every call
- local os_type, os_name = os.type, os.name
- local filecollapsepath = filecollapsepath
- local lfsreadlink = lfs.readlink
-
- --- windows and dos
- if os_type == "windows" or os_type == "msdos" then
- --- ms platfom specific stuff
- path_normalize = function (path)
- path = stringgsub(path, '\\', '/')
- path = stringlower(path)
- path = filecollapsepath(path)
- return path
- end
---[[doc--
- The special treatment for cygwin was removed with a patch submitted
- by Ken Brown.
- Reference: http://cygwin.com/ml/cygwin/2013-05/msg00006.html
---doc]]--
-
- else -- posix
- path_normalize = function (path)
- local dest = lfsreadlink(path)
- if dest then
- if kpsereadable_file(dest) then
- path = dest
- elseif kpsereadable_file(filejoin(filedirname(path), dest)) then
- path = filejoin(file.dirname(path), dest)
- else
- -- broken symlink?
- end
- end
- path = filecollapsepath(path)
- return path
- end
- end
-end
-
-fonts.path_normalize = path_normalize
-
-names.blacklist = { }
-
-local blacklist = names.blacklist
-local p_blacklist --- prefixes of dirs
-
---- string list -> string list
-local collapse_prefixes = function (lst)
- --- avoid redundancies in blacklist
- if #lst < 2 then
- return lst
- end
-
- tablesort(lst)
- local cur = lst[1]
- local result = { cur }
- for i=2, #lst do
- local elm = lst[i]
- if stringsub(elm, 1, #cur) ~= cur then
- --- different prefix
- cur = elm
- result[#result+1] = cur
- end
- end
- return result
-end
-
---- string list -> string list -> (string, bool) hash_t
-local create_blacklist = function (blacklist, whitelist)
- local result = { }
- local dirs = { }
-
- report("info", 2, "db", "Blacklisting %d files and directories.",
- #blacklist)
- for i=1, #blacklist do
- local entry = blacklist[i]
- if lfsisdir(entry) then
- dirs[#dirs+1] = entry
- else
- result[blacklist[i]] = true
- end
- end
-
- report("info", 2, "db", "Whitelisting %d files.", #whitelist)
- for i=1, #whitelist do
- result[whitelist[i]] = nil
- end
-
- dirs = collapse_prefixes(dirs)
-
- --- build the disjunction of the blacklisted directories
- for i=1, #dirs do
- local p_dir = P(dirs[i])
- if p_blacklist then
- p_blacklist = p_blacklist + p_dir
- else
- p_blacklist = p_dir
- end
- end
-
- if p_blacklist == nil then
- --- always return false
- p_blacklist = Cc(false)
- end
-
- return result
-end
-
---- unit -> unit
-read_blacklist = function ()
- local files = {
- kpselookup ("luaotfload-blacklist.cnf",
- {all=true, format="tex"})
- }
- local blacklist = { }
- local whitelist = { }
-
- if files and type(files) == "table" then
- for _, path in next, files do
- for line in iolines (path) do
- line = stringstrip(line) -- to get rid of lines like " % foo"
- local first_chr = stringsub(line, 1, 1)
- if first_chr == "%" or stringis_empty(line) then
- -- comment or empty line
- elseif first_chr == "-" then
- report ("both", 3, "db",
- "Whitelisted file %q via %q.",
- line, path)
- whitelist[#whitelist+1] = stringsub(line, 2, -1)
- else
- local cmt = stringfind(line, "%%")
- if cmt then
- line = stringsub(line, 1, cmt - 1)
- end
- line = stringstrip(line)
- report ("both", 3, "db",
- "Blacklisted file %q via %q.",
- line, path)
- blacklist[#blacklist+1] = line
- end
- end
- end
- end
- names.blacklist = create_blacklist(blacklist, whitelist)
-end
-
-local p_font_filter
-
-do
- local current_formats = { }
-
- local extension_pattern = function (list)
- local pat
- for i=#list, 1, -1 do
- local e = list[i]
- if not pat then
- pat = P(e)
- else
- pat = pat + P(e)
- end
- end
- pat = pat * P(-1)
- return (1 - pat)^1 * pat
- end
-
- --- small helper to adjust the font filter pattern (--formats
- --- option)
-
- set_font_filter = function (formats)
-
- if not formats or type (formats) ~= "string" then
- return
- end
-
- if stringsub (formats, 1, 1) == "+" then -- add
- formats = lpegmatch (splitcomma, stringsub (formats, 2))
- if formats then
- current_formats = tableappend (current_formats, formats)
- end
- elseif stringsub (formats, 1, 1) == "-" then -- add
- formats = lpegmatch (splitcomma, stringsub (formats, 2))
- if formats then
- local newformats = { }
- for i = 1, #current_formats do
- local fmt = current_formats[i]
- local include = true
- for j = 1, #formats do
- if current_formats[i] == formats[j] then
- include = false
- goto skip
- end
- end
- newformats[#newformats+1] = fmt
- ::skip::
- end
- current_formats = newformats
- end
- else -- set
- formats = lpegmatch (splitcomma, formats)
- if formats then
- current_formats = formats
- end
- end
-
- p_font_filter = extension_pattern (current_formats)
- end
-
- get_font_filter = function (formats)
- return tablefastcopy (current_formats)
- end
-
- --- initialize
- set_font_filter (luaotfloadconfig.formats)
-end
-
-local process_dir_tree
-process_dir_tree = function (acc, dirs)
- if not next (dirs) then --- done
- return acc
- end
-
- local pwd = lfscurrentdir ()
- local dir = dirs[#dirs]
- dirs[#dirs] = nil
-
- if lfschdir (dir) then
- lfschdir (pwd)
-
- local newfiles = { }
- local blacklist = names.blacklist
- for ent in lfsdir (dir) do
- --- filter right away
- if ent ~= "." and ent ~= ".." and not blacklist[ent] then
- local fullpath = dir .. "/" .. ent
- if lfsisdir (fullpath)
- and not lpegmatch (p_blacklist, fullpath)
- then
- dirs[#dirs+1] = fullpath
- elseif lfsisfile (fullpath) then
- ent = stringlower (ent)
-
- if lpegmatch (p_font_filter, ent) then
- if filesuffix (ent) == "afm" then
- --- fontloader.open() will load the afm
- --- iff both files are in the same directory
- local pfbpath = filereplacesuffix
- (fullpath, "pfb")
- if lfsisfile (pfbpath) then
- newfiles[#newfiles+1] = pfbpath
- end
- else
- newfiles[#newfiles+1] = fullpath
- end
- end
-
- end
- end
- end
- return process_dir_tree (tableappend (acc, newfiles), dirs)
- end
- --- cannot cd; skip
- return process_dir_tree (acc, dirs)
-end
-
-local process_dir = function (dir)
- local pwd = lfscurrentdir ()
- if lfschdir (dir) then
- lfschdir (pwd)
-
- local files = { }
- local blacklist = names.blacklist
- for ent in lfsdir (dir) do
- if ent ~= "." and ent ~= ".." and not blacklist[ent] then
- local fullpath = dir .. "/" .. ent
- if lfsisfile (fullpath) then
- ent = stringlower (ent)
- if lpegmatch (p_font_filter, ent)
- then
- if filesuffix (ent) == "afm" then
- --- fontloader.open() will load the afm
- --- iff both files are in the same
- --- directory
- local pfbpath = filereplacesuffix
- (fullpath, "pfb")
- if lfsisfile (pfbpath) then
- files[#files+1] = pfbpath
- end
- else
- files[#files+1] = fullpath
- end
- end
- end
- end
- end
- return files
- end
- return { }
-end
-
---- string -> bool -> string list
-local find_font_files = function (root, recurse)
- if lfsisdir (root) then
- if recurse == true then
- return process_dir_tree ({}, { root })
- else --- kpathsea already delivered the necessary subdirs
- return process_dir (root)
- end
- end
-end
-
---- truncate_string -- Cut the first part of a string to fit it
---- into a given terminal width. The parameter “restrict” (int)
---- indicates the number of characters already consumed on the
---- line.
-local truncate_string = function (str, restrict)
- local tw = luaotfloadconfig.termwidth
- local wd = tw - restrict
- local len = utf8len (str)
- if wd - len < 0 then
- --- combined length exceeds terminal,
- str = ".." .. stringsub(str, len - wd + 2)
- end
- return str
-end
-
---[[doc--
-
- scan_dir() scans a directory and populates the list of fonts
- with all the fonts it finds.
-
- · 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, currentnames, targetnames,
- dry_run, location)
- if lpegmatch (p_blacklist, dirname) then
- report ("both", 4, "db",
- "Skipping blacklisted directory %s.", dirname)
- --- ignore
- return 0, 0
- end
- local found = find_font_files (dirname, location ~= "texmf")
- if not found then
- report ("both", 4, "db",
- "No such directory: %q; skipping.", dirname)
- return 0, 0
- end
- report ("both", 4, "db", "Scanning directory %s.", dirname)
-
- local n_new = 0 --- total of fonts collected
- local n_found = #found
- local max_fonts = luaotfloadconfig.max_fonts
-
- report ("both", 4, "db", "%d font files detected.", n_found)
- for j=1, n_found do
- if max_fonts and fonts_read >= max_fonts then
- break
- end
-
- local fullname = found[j]
- fullname = path_normalize(fullname)
- local new
-
- if dry_run == true then
- local truncated = truncate_string (fullname, 43)
- report ("log", 2, "db",
- "Would have been loading %s.", fullname)
- report_status ("term", "db",
- "Would have been loading %s", truncated)
- else
- local truncated = truncate_string (fullname, 32)
- report ("log", 2, "db", "Loading font %s.", fullname)
- report_status ("term", "db", "Loading font %s", truncated)
- local new = read_font_names (fullname, currentnames,
- targetnames, texmf)
- if new == true then
- fonts_read = fonts_read + 1
- n_new = n_new + 1
- end
- end
- end
- report ("both", 4, "db", "Done. %d fonts indexed in %q.",
- n_found, dirname)
- return n_found, n_new
-end
-
---- string list -> string list
-local filter_out_pwd = function (dirs)
- local result = { }
- local pwd = path_normalize (lpegmatch (stripslashes,
- lfscurrentdir ()))
- for i = 1, #dirs do
- --- better safe than sorry
- local dir = path_normalize (lpegmatch (stripslashes, dirs[i]))
- if not (dir == "." or dir == pwd) then
- result[#result+1] = dir
- end
- end
- return result
-end
-
-local path_separator = ostype == "windows" and ";" or ":"
-
---[[doc--
-
- scan_texmf_fonts() scans all fonts in the texmf tree through the
- kpathsea variables OPENTYPEFONTS and TTFONTS of texmf.cnf.
- The current working directory comes as “.” (texlive) or absolute
- path (miktex) and will always be filtered out.
-
---doc]]--
-
---- dbobj -> dbobj -> bool? -> (int * int)
-
-local scan_texmf_fonts = function (currentnames, targetnames, dry_run)
-
- local n_scanned, n_new, fontdirs = 0, 0
- local osfontdir = kpseexpand_path "$OSFONTDIR"
-
- if stringis_empty (osfontdir) then
- report ("info", 1, "db", "Scanning TEXMF fonts...")
- else
- report ("info", 1, "db", "Scanning TEXMF and OS fonts...")
- if log.get_loglevel () > 3 then
- local osdirs = filesplitpath (osfontdir)
- report ("info", 0, "db",
- "$OSFONTDIR has %d entries:", #osdirs)
- for i = 1, #osdirs do
- report ("info", 0, "db", "[%d] %s", i, osdirs[i])
- end
- end
- end
-
- fontdirs = kpseexpand_path "$OPENTYPEFONTS"
- fontdirs = fontdirs .. path_separator .. kpseexpand_path "$TTFONTS"
- fontdirs = fontdirs .. path_separator .. kpseexpand_path "$T1FONTS"
-
- if not stringis_empty (fontdirs) then
- local tasks = filter_out_pwd (filesplitpath (fontdirs))
- report ("info", 3, "db",
- "Initiating scan of %d directories.", #tasks)
- report_status_start (2, 4)
- for _, d in next, tasks do
- local found, new = scan_dir (d, currentnames, targetnames,
- dry_run, "texmf")
- n_scanned = n_scanned + found
- n_new = n_new + new
- end
- report_status_stop ("term", "db", "Scanned %d files, %d new.", n_scanned, n_new)
- end
-
- return n_scanned, n_new
-end
-
---- TODO stuff those paths into some writable table
---- unit -> string list
-local function get_os_dirs ()
- if os.name == 'macosx' then
- return {
- filejoin(kpseexpand_path('~'), "Library/Fonts"),
- "/Library/Fonts",
- "/System/Library/Fonts",
- "/Network/Library/Fonts",
- }
- elseif os.type == "windows" or os.type == "msdos" then
- local windir = osgetenv("WINDIR")
- return { filejoin(windir, 'Fonts') }
- else
- local fonts_conves = { --- plural, much?
- "/usr/local/etc/fonts/fonts.conf",
- "/etc/fonts/fonts.conf",
- }
- local os_dirs = read_fonts_conf(fonts_conves, find_files)
- return os_dirs
- end
- return {}
-end
-
---[[doc--
-
- scan_os_fonts() scans the OS fonts through
- - fontconfig for Unix (reads the fonts.conf file[s] and scans the
- directories)
- - a static set of directories for Windows and MacOSX
-
- **NB**: If $OSFONTDIR is nonempty, as it appears to be by default
- on Windows setups, the system fonts will have already been
- processed while scanning the TEXMF. Thus, this function is
- never called.
-
---doc]]--
-
---- dbobj -> dbobj -> bool? -> (int * int)
-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",
- "Searching in static system directories...")
-
- report_status_start (2, 4)
- for _, d in next, get_os_dirs () do
- local found, new = scan_dir (d, currentnames,
- targetnames, dry_run)
- n_scanned = n_scanned + found
- n_new = n_new + new
- end
- report_status_stop ("term", "db", "Scanned %d files, %d new.", n_scanned, n_new)
-
- return n_scanned, n_new
-end
-
---- unit -> (bool, lookup_cache)
-flush_lookup_cache = function ()
- lookup_cache = { }
- collectgarbage "collect"
- return true, lookup_cache
-end
-
-
---- fontentry list -> filemap
-
-local generate_filedata = function (mappings)
-
- report ("both", 2, "db", "Creating filename map.")
-
- local nmappings = #mappings
-
- 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 base = files.base
- local bare = files.bare
- local full = files.full
-
- local conflicts = {
- basenames = 0,
- barenames = 0,
- }
-
- 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
- format = entry.format --- otf, afm, ...
- location = entry.location --- texmf, system, ...
- fullpath = entry.fullpath
- basename = entry.basename
- barename = filenameonly (fullpath)
- subfont = entry.subfont
- end
-
- 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
-
- else
- inbare [barename] = index
- end
- else
- inbare = { [barename] = index }
- bare [location] [format] = inbare
- end
-
- --- 3) add to fullpath map
-
- full [index] = fullpath
- end
-
- return files
-end
-
-local pick_style
-local check_regular
-
-do
- local splitfontname = lpeg.splitat "-"
-
- local choose_exact = function (field)
- --- only clean matches, without guessing
- if italic_synonym [field] then
- return "i"
- end
-
- if field == "bold" then
- return "b"
- end
-
- if 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 then
- style = choose_exact (fontstyle_name)
- end
- if not style then
- if prefmodifiers then
- style = choose_exact (prefmodifiers)
- elseif subfamily then
- style = choose_exact (subfamily)
- end
- end
- return style
- end
-
- pick_fallback_style = function (italicangle, weight)
- --- more aggressive, but only to determine bold faces
- if weight > 500 then --- bold spectrum matches
- if italicangle == 0 then
- return tostring (weight)
- else
- return tostring (weight) .. "i"
- end
- end
- return false
- 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,
- italicangle,
- weight)
-
- if fontstyle_name then
- return regular_synonym [fontstyle_name]
- elseif prefmodifiers then
- return regular_synonym [prefmodifiers]
- elseif subfamily then
- return regular_synonym [subfamily]
- elseif splitstyle then
- return regular_synonym [splitstyle]
- elseif italicangle == 0 and weight == 400 then
- return true
- end
-
- return nil
- end
-end
-
-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
- local metadata = sanitized.metadata
-
- --- 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 or metadata.fontname
- entry.fullname = english.fullname or info.fullname
- entry.splainname = metadata.fullname
- entry.prefmodifiers = english.prefmodifiers
- local metafamily = metadata.familyname
- local familyname = english.preffamily or english.family
- entry.familyname = familyname
- if familyname ~= metafamily then
- entry.metafamily = metadata.familyname
- end
- entry.fontstyle_name = sanitized.fontstyle_name
- entry.plainname = names.fullname
- 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
- entry.file = nil
- entry.names = nil
- entry.style = nil
- end
-end
-
-local add_family = function (name, subtable, modifier, entry)
- if not name then --- probably borked font
- return
- end
- local familytable = subtable [name]
- if not familytable then
- familytable = { }
- subtable [name] = familytable
- end
-
- local size = entry.size
-
- familytable [#familytable + 1] = {
- index = entry.index,
- modifier = modifier,
- }
-end
-
-local get_subtable = function (families, entry)
- local location = entry.location
- local format = entry.format
- local subtable = families [location] [format]
- if not subtable then
- subtable = { }
- families [location] [format] = subtable
- end
- return subtable
-end
-
-local collect_families = function (mappings)
-
- report ("info", 2, "db", "Analyzing families.")
-
- local families = {
- ["local"] = { },
- system = { },
- texmf = { },
- }
-
- for i = 1, #mappings do
-
- local entry = mappings [i]
-
- if entry.file then
- pull_values (entry)
- end
-
- local subtable = get_subtable (families, entry)
-
- local familyname = entry.familyname
- local metafamily = entry.metafamily
- 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 --- regular, exact only
- modifier = check_regular (fontstyle_name,
- prefmodifiers,
- subfamily,
- splitstyle,
- italicangle,
- weight)
- end
-
- if modifier then
- add_family (familyname, subtable, modifier, entry)
- --- registering the metafamilies is unreliable within the
- --- same table as identifiers might interfere with an
- --- unmarked style that lacks a metafamily, e.g.
- ---
- --- iwona condensed regular ->
- --- family: iwonacond
- --- metafamily: iwona
- --- iwona regular ->
- --- family: iwona
- --- metafamily: ø
- ---
- --- Both would be registered as under the same family,
- --- i.e. “iwona”, and depending on the loading order
- --- the query “name:iwona” can resolve to the condensed
- --- version instead of the actual unmarked one. The only
- --- way around this would be to introduce a separate
- --- table for metafamilies and do fallback queries on it.
- --- At the moment this is not pressing enough to justify
- --- further increasing the index size, maybe if need
- --- arises from the user side.
--- if metafamily and metafamily ~= familyname then
--- add_family (metafamily, subtable, modifier, entry)
--- end
- elseif weight > 500 then -- in bold spectrum
- modifier = pick_fallback_style (italicangle, weight)
- if modifier then
- add_family (familyname, subtable, modifier, entry)
- end
- end
- end
-
- collectgarbage "collect"
- return families
-end
-
---[[doc--
-
- add_bold_spectrum -- For not-quite-bold faces, determine whether
- they can fill in for a missing bold face slot in a matching family.
-
- Some families like Lucida do not contain real bold / bold italic
- members. Instead, they have semibold variants at weight 600 which
- we must add in a separate pass.
-
---doc]]--
-
-local bold_spectrum_low = 501 --- 500 is medium, 900 heavy/black
-local bold_weight = 700
-local style_categories = { "r", "b", "i", "bi" }
-local bold_categories = { "b", "bi" }
-
-local group_modifiers = function (mappings, families)
- report ("info", 2, "db", "Analyzing shapes, weights, and styles.")
- for location, location_data in next, families do
- for format, format_data in next, location_data do
- for familyname, collected in next, format_data do
- local styledata = { } --- will replace the “collected” table
- --- First, fill in the ordinary style data that
- --- fits neatly into the four relevant modifier
- --- categories.
- for _, modifier in next, style_categories do
- local entries
- for key, info in next, collected do
- if info.modifier == modifier then
- if not entries then
- entries = { }
- end
- local index = info.index
- local entry = mappings [index]
- local size = entry.size
- if size then
- entries [#entries + 1] = {
- size [1],
- size [2],
- size [3],
- index,
- }
- else
- entries.default = index
- end
- collected [key] = nil
- end
- styledata [modifier] = entries
- end
- end
-
- --- At this point the family set may still lack
- --- entries for bold or bold italic. We will fill
- --- those in using the modifier with the numeric
- --- weight that is closest to bold (700).
- if next (collected) then --- there are uncategorized entries
- for _, modifier in next, bold_categories do
- if not styledata [modifier] then
- local closest
- local minimum = 2^51
- for key, info in next, collected do
- local info_modifier = tonumber (info.modifier) and "b" or "bi"
- if modifier == info_modifier then
- local index = info.index
- local entry = mappings [index]
- local weight = entry.weight
- local diff = weight < 700 and 700 - weight or weight - 700
- if diff < minimum then
- minimum = diff
- closest = weight
- end
- end
- end
- if closest then
- --- We know there is a substitute face for the modifier.
- --- Now we scan the list again to extract the size data
- --- in case the shape is available at multiple sizes.
- local entries = { }
- for key, info in next, collected do
- local info_modifier = tonumber (info.modifier) and "b" or "bi"
- if modifier == info_modifier then
- local index = info.index
- local entry = mappings [index]
- local size = entry.size
- if entry.weight == closest then
- if size then
- entries [#entries + 1] = {
- size [1],
- size [2],
- size [3],
- index,
- }
- else
- entries.default = index
- end
- end
- end
- end
- styledata [modifier] = entries
- end
- end
- end
- end
- format_data [familyname] = styledata
- end
- end
- end
- 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 log.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 (currentnames, force, dry_run)
-
- local targetnames
-
- if luaotfloadconfig.update_live == false then
- report ("info", 2, "db",
- "Skipping database update.")
- --- skip all db updates
- return currentnames or name_index
- end
-
- local starttime = osgettimeofday ()
- local n_rawnames, n_newnames = 0, 0
-
- --[[
- The main function, scans everything
- - “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 "."))
-
- --- 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 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
-
- targetnames = initialize_namedata (get_font_filter ())
-
- read_blacklist ()
-
- 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
-
- --- 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: arrange style and size info
- targetnames.families = group_modifiers (targetnames.mappings,
- targetnames.families)
-
- --- pass 6: 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
-
- save_names ()
-
- local success, _lookups = flush_lookup_cache ()
- if success then
- local success = save_lookups ()
- if success then
- report ("info", 2, "cache",
- "Lookup cache emptied.")
- return targetnames
- end
- end
- end
- return targetnames
-end
-
---- unit -> bool
-save_lookups = function ( )
- local path = names.path.lookups
- local luaname, lucname = path.lua, path.luc
- if fileiswritable (luaname) and fileiswritable (lucname) then
- tabletofile (luaname, lookup_cache, true)
- osremove (lucname)
- caches.compile (lookup_cache, luaname, lucname)
- --- double check ...
- if lfsisfile (luaname) and lfsisfile (lucname) then
- report ("both", 3, "cache", "Lookup cache saved.")
- return true
- end
- report ("info", 0, "cache", "Could not compile lookup cache.")
- return false
- end
- report ("info", 0, "cache", "Lookup cache file not writable.")
- if not fileiswritable (luaname) then
- report ("info", 0, "cache", "Failed to write %s.", luaname)
- end
- if not fileiswritable (lucname) then
- report ("info", 0, "cache", "Failed to write %s.", lucname)
- end
- return false
-end
-
---- save_names() is usually called without the argument
---- dbobj? -> bool
-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
- osremove (lucname)
- local gzname = luaname .. ".gz"
- if luaotfloadconfig.compress then
- local serialized = tableserialize (currentnames, true)
- save_gzipped (gzname, serialized)
- caches.compile (currentnames, "", lucname)
- else
- tabletofile (luaname, currentnames, true)
- caches.compile (currentnames, luaname, lucname)
- end
- report ("info", 2, "db", "Font index saved at ...")
- local success = false
- if lfsisfile (luaname) then
- report ("info", 2, "db", "Text: " .. luaname)
- success = true
- end
- if lfsisfile (gzname) then
- report ("info", 2, "db", "Gzip: " .. gzname)
- success = true
- end
- if lfsisfile (lucname) then
- report ("info", 2, "db", "Byte: " .. lucname)
- success = true
- end
- if success then
- return true
- else
- report ("info", 0, "db", "Could not compile font index.")
- return false
- end
- end
- report ("info", 0, "db", "Index file not writable")
- if not fileiswritable (luaname) then
- report ("info", 0, "db", "Failed to write %s.", luaname)
- end
- if not fileiswritable (lucname) then
- report ("info", 0, "db", "Failed to write %s.", lucname)
- end
- return false
-end
-
---[[doc--
-
- Below set of functions is modeled after mtx-cache.
-
---doc]]--
-
---- string -> string -> string list -> string list -> string list -> unit
-local print_cache = function (category, path, luanames, lucnames, rest)
- local report_indeed = function (...)
- report("info", 0, "cache", ...)
- end
- report_indeed("Luaotfload cache: %s", category)
- report_indeed("location: %s", path)
- report_indeed("[raw] %4i", #luanames)
- report_indeed("[compiled] %4i", #lucnames)
- report_indeed("[other] %4i", #rest)
- report_indeed("[total] %4i", #luanames + #lucnames + #rest)
-end
-
---- string -> string -> string list -> bool -> bool
-local purge_from_cache = function (category, path, list, all)
- report("info", 1, "cache", "Luaotfload cache: %s %s",
- (all and "erase" or "purge"), category)
- report("info", 1, "cache", "location: %s",path)
- local n = 0
- for i=1,#list do
- local filename = list[i]
- if stringfind(filename,"luatex%-cache") then -- safeguard
- if all then
- report("info", 5, "cache", "Removing %s.", filename)
- osremove(filename)
- n = n + 1
- else
- local suffix = filesuffix(filename)
- if suffix == "lua" then
- local checkname = file.replacesuffix(
- filename, "lua", "luc")
- if lfsisfile(checkname) then
- report("info", 5, "cache", "Removing %s.", filename)
- osremove(filename)
- n = n + 1
- end
- end
- end
- end
- end
- report("info", 1, "cache", "Removed lua files : %i", n)
- return true
-end
-
---- string -> string list -> int -> string list -> string list -> string list ->
---- (string list * string list * string list * string list)
-local collect_cache collect_cache = function (path, all, n, luanames,
- lucnames, rest)
- if not all then
- local all = find_files (path)
-
- local luanames, lucnames, rest = { }, { }, { }
- return collect_cache(nil, all, 1, luanames, lucnames, rest)
- end
-
- local filename = all[n]
- if filename then
- local suffix = filesuffix(filename)
- if suffix == "lua" then
- luanames[#luanames+1] = filename
- elseif suffix == "luc" then
- lucnames[#lucnames+1] = filename
- else
- rest[#rest+1] = filename
- end
- return collect_cache(nil, all, n+1, luanames, lucnames, rest)
- end
- return luanames, lucnames, rest, all
-end
-
-local getwritablecachepath = function ( )
- --- fonts.handlers.otf doesn’t exist outside a Luatex run,
- --- so we have to improvise
- local writable = getwritablepath (luaotfloadconfig.cache_dir)
- if writable then
- return writable
- end
-end
-
-local getreadablecachepaths = function ( )
- local readables = caches.getreadablepaths
- (luaotfloadconfig.cache_dir)
- local result = { }
- if readables then
- for i=1, #readables do
- local readable = readables[i]
- if lfsisdir (readable) then
- result[#result+1] = readable
- end
- end
- end
- return result
-end
-
---- unit -> unit
-local purge_cache = function ( )
- local writable_path = getwritablecachepath ()
- local luanames, lucnames, rest = collect_cache(writable_path)
- if log.get_loglevel() > 1 then
- print_cache("writable path", writable_path, luanames, lucnames, rest)
- end
- local success = purge_from_cache("writable path", writable_path, luanames, false)
- return success
-end
-
---- unit -> unit
-local erase_cache = function ( )
- local writable_path = getwritablecachepath ()
- local luanames, lucnames, rest, all = collect_cache(writable_path)
- if log.get_loglevel() > 1 then
- print_cache("writable path", writable_path, luanames, lucnames, rest)
- end
- local success = purge_from_cache("writable path", writable_path, all, true)
- return success
-end
-
-local separator = function ( )
- report("info", 0, string.rep("-", 67))
-end
-
---- unit -> unit
-local show_cache = function ( )
- local readable_paths = getreadablecachepaths ()
- local writable_path = getwritablecachepath ()
- local luanames, lucnames, rest = collect_cache(writable_path)
-
- separator ()
- print_cache ("writable path", writable_path,
- luanames, lucnames, rest)
- texio.write_nl""
- for i=1,#readable_paths do
- local readable_path = readable_paths[i]
- if readable_path ~= writable_path then
- local luanames, lucnames = collect_cache (readable_path)
- print_cache ("readable path",
- readable_path, luanames, lucnames, rest)
- end
- end
- separator()
- return true
-end
-
------------------------------------------------------------------------
---- export functionality to the namespace “fonts.names”
------------------------------------------------------------------------
-
-names.scan_dir = scan_dir
-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_fontname = sanitize_fontname
-names.getfilename = resolve_fullpath
-names.set_location_precedence = set_location_precedence
-
---- font cache
-names.purge_cache = purge_cache
-names.erase_cache = erase_cache
-names.show_cache = show_cache
-
---- replace the resolver from luatex-fonts
-if luaotfloadconfig.resolver == "cached" then
- report("both", 2, "cache", "Caching of name: lookups active.")
- names.resolvespec = resolve_cached
- names.resolve_name = resolve_cached
-else
- names.resolvespec = resolve_name
- names.resolve_name = resolve_name
-end
-
-names.find_closest = find_closest
-
--- for testing purpose
-names.read_fonts_conf = read_fonts_conf
-
--- vim:tw=71:sw=4:ts=4:expandtab