summaryrefslogtreecommitdiff
path: root/src/luaotfload-tool.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/luaotfload-tool.lua')
-rwxr-xr-xsrc/luaotfload-tool.lua1263
1 files changed, 1263 insertions, 0 deletions
diff --git a/src/luaotfload-tool.lua b/src/luaotfload-tool.lua
new file mode 100755
index 0000000..35765b5
--- /dev/null
+++ b/src/luaotfload-tool.lua
@@ -0,0 +1,1263 @@
+#!/usr/bin/env texlua
+-----------------------------------------------------------------------
+-- FILE: luaotfload-tool.lua
+-- DESCRIPTION: database functionality
+-- REQUIREMENTS: luaotfload 2.5
+-- AUTHOR: Khaled Hosny, Élie Roux, Philipp Gesang
+-- VERSION: 2.5
+-- LICENSE: GPL v2.0
+-- MODIFIED: 2014-01-14 13:17:04+0100
+-----------------------------------------------------------------------
+
+luaotfload = luaotfload or { }
+local version = "2.5" --- <int: major>.<int: minor>-<int: fixes>
+luaotfload.version = version
+luaotfload.self = "luaotfload-tool"
+
+--[[doc--
+
+luaotfload-tool(1)
+
+This file was originally written (as \fileent{mkluatexfontdb.lua}) by
+Elie Roux and Khaled Hosny and, as a derived work of ConTeXt, is
+provided under the terms of the GPL v2.0 license as printed in full
+text in the manual (luaotfload.pdf).
+
+ \url{http://www.gnu.org/licenses/old-licenses/gpl-2.0.html}.
+
+This file is a wrapper for the luaotfload font names module
+(luaotfload-database.lua). It is part of the luaotfload bundle, please
+see the luaotfload documentation for more info. Report bugs to
+
+ \url{https://github.com/lualatex/luaotfload/issues}.
+
+--doc]]--
+
+kpse.set_program_name "luatex"
+
+--[[doc--
+
+ We test for Lua 5.1 by means of capability detection to see if
+ we’re running an outdated Luatex. If so, we bail.
+
+ \url{http://lua-users.org/wiki/LuaVersionCompatibility}
+
+--doc]]--
+
+
+local ioopen = io.open
+local iowrite = io.write
+local kpsefind_file = kpse.find_file
+local next = next
+local osdate = os.date
+local ostype = os.type
+local stringexplode = string.explode
+local stringformat = string.format
+local stringlower = string.lower
+local stringrep = string.rep
+local tableconcat = table.concat
+local texiowrite_nl = texio.write_nl
+local texiowrite = texio.write
+local tonumber = tonumber
+local type = type
+
+local runtime
+if _G.getfenv ~= nil then -- 5.1 or LJ
+ if _G.jit ~= nil then
+ runtime = { "jit", jit.version }
+ else
+ runtime = { "stock", _VERSION }
+ print "FATAL ERROR"
+ print "Luaotfload requires a Luatex version >=0.76."
+ print "Please update your TeX distribution!"
+ os.exit (-1)
+ end
+else -- 5.2
+ runtime = { "stock", _VERSION }
+end
+
+
+local C, Ct, P, S = lpeg.C, lpeg.Ct, lpeg.P, lpeg.S
+local lpegmatch = lpeg.match
+
+local loader_file = "luatexbase.loader.lua"
+local loader_path = assert(kpsefind_file(loader_file, "lua"),
+ "File '"..loader_file.."' not found")
+
+
+string.quoted = string.quoted or function (str)
+ return string.format("%q",str)
+end
+
+require(loader_path)
+
+config = config or { }
+local config = config
+local luaotfloadconfig = config.luaotfload or { }
+config.luaotfload = luaotfloadconfig
+luaotfloadconfig.version = luaotfloadconfig.version or version
+luaotfloadconfig.names_dir = luaotfloadconfig.names_dir or "names"
+luaotfloadconfig.cache_dir = luaotfloadconfig.cache_dir or "fonts"
+luaotfloadconfig.index_file = luaotfloadconfig.index_file
+ or "luaotfload-names.lua"
+luaotfloadconfig.formats = luaotfloadconfig.formats
+ or "otf,ttf,ttc,dfont"
+luaotfloadconfig.reload = false
+if not luaotfloadconfig.strip then
+ luaotfloadconfig.strip = true
+end
+
+config.lualibs = config.lualibs or { }
+config.lualibs.verbose = false
+config.lualibs.prefer_merged = true
+config.lualibs.load_extended = true
+
+require "lualibs"
+local tabletohash = table.tohash
+local stringsplit = string.split
+
+--[[doc--
+\fileent{luatex-basics-gen.lua} calls functions from the
+\luafunction{texio.*} library; too much for our taste.
+We intercept them with dummies.
+
+Also, it sets up dummies in place of the tables created by the Context
+libraries. Since we have loaded the lualibs already this would cause
+collateral damage for some libraries whose namespace would be
+overridden. We employ our usual backup-restore strategy to work around
+this. (Postponing the loading of the lualibs code is not an option
+because the functionality is needed by basics-gen itself.)
+--doc]]--
+
+local dummy_function = function ( ) end
+local backup = {
+ write = texio.write,
+ write_nl = texio.write_nl,
+ utilities = utilities,
+}
+
+texio.write, texio.write_nl = dummy_function, dummy_function
+require"luaotfload-basics-gen.lua"
+
+texio.write, texio.write_nl = backup.write, backup.write_nl
+utilities = backup.utilities
+
+require"luaotfload-log.lua" --- this populates the luaotfload.log.* namespace
+require"luaotfload-parsers" --- fonts.conf and request syntax
+require"luaotfload-database"
+require"alt_getopt"
+
+local names = fonts.names
+local status_file = "luaotfload-status"
+local luaotfloadstatus = require (status_file)
+luaotfloadconfig.status = luaotfloadstatus
+local sanitize_fontname = names.sanitize_fontname
+
+local log = luaotfload.log
+local report = log.report
+
+local pathdata = names.path
+local names_plain = pathdata.index.lua
+local names_gzip = names_plain .. ".gz"
+local names_bin = pathdata.index.luc
+
+local help_messages = {
+ ["luaotfload-tool"] = [[
+
+Usage: %s [OPTIONS...]
+
+ Luaotfload font management and diagnostic utility.
+ This program is part of the Luaotfload package.
+
+ Valid options are:
+
+-------------------------------------------------------------------------------
+ VERBOSITY AND DIAGNOSTICS
+
+ -q --quiet don't output anything
+ -v --verbose=LEVEL be more verbose (print the searched directories)
+ -vv print the loaded fonts
+ -vvv print all steps of directory searching
+ --log=stdout redirect log output to stdout
+
+ -V --version print version and exit
+ -h --help print this message
+ --diagnose=CHECK run a self test procedure; one of "files",
+ "environment", "index", "permissions", or
+ "repository"
+
+-------------------------------------------------------------------------------
+ DATABASE
+
+ -u --update update the database
+ -n --no-reload suppress db update
+ --no-strip keep redundant information in db
+ -f --force force re-indexing all fonts
+ -c --no-compress do not gzip index file (text version only)
+ -l --flush-lookups empty lookup cache of font requests
+ -D --dry-run skip loading of fonts, just scan
+ --formats=[+|-]EXTENSIONS set, add, or subtract formats to index
+ -p --prefer-texmf prefer fonts in the TEXMF over system fonts
+ --max-fonts=N process at most N font files
+
+ --find="font name" query the database for a font name
+ -F --fuzzy look for approximate matches if --find fails
+ --limit=n limit display of fuzzy matches to <n>
+ (default: n = 1)
+
+ -i --info display basic font metadata
+ -I --inspect display detailed font metadata
+ -w --warnings display warnings generated by the
+ fontloader library
+
+ --list=<criterion> output list of entries by field <criterion>
+ --list=<criterion>:<value> restrict to entries with <criterion>=<value>
+ --fields=<f1>,<f2>,…,<fn> which fields <f> to print with --list
+ -b --show-blacklist show blacklisted files
+
+The font database will be saved to
+ %s
+ %s
+
+-------------------------------------------------------------------------------
+ FONT CACHE
+
+ --cache=<directive> operate on font cache, where <directive> is
+ "show", "purge", or "erase"
+
+The font cache will be written to
+ %s
+
+]],
+ mkluatexfontdb = [[
+FATAL ERROR
+As of Luaotfload v2.5, legacy behavior is not supported anymore. Please
+update your scripts and/or habits! Kthxbye.
+]],
+ short = [[
+Usage: luaotfload-tool [--help] [--version] [--verbose=<lvl>]
+ [--update] [--force] [--prefer-texmf]
+ [--dry-run] [--formats=<extension list>]
+ [--find=<font name>] [--fuzzy] [--info] [--inspect]
+ [--list=<criterion>] [--fields=<field list>]
+ [--cache=<directive>] [--flush-lookups]
+ [--show-blacklist] [--diagnose=<procedure>]
+
+Enter 'luaotfload-tool --help' for a larger list of options.
+]]
+}
+
+local help_msg = function (version)
+ local template = help_messages[version]
+ iowrite(stringformat(template,
+ luaotfload.self,
+-- names_plain,
+ names_gzip,
+ names_bin,
+ caches.getwritablepath (
+ luaotfloadconfig.cache_dir)))
+end
+
+local about = [[
+%s:
+ Luaotfload font management and diagnostic utility.
+ License: GNU GPL v2.0.
+ Report problems to <https://github.com/lualatex/luaotfload/issues>
+]]
+
+local version_msg = function ( )
+ local out = function (...) texiowrite_nl (stringformat (...)) end
+ out (about, luaotfload.self)
+ out ("%s version %q", luaotfload.self, version)
+ out ("revision %q", luaotfloadstatus.notes.revision)
+ out ("database version %q", names.version)
+ out ("Lua interpreter: %s; version %q", runtime[1], runtime[2])
+ out ("Luatex SVN revision %d", status.luatex_svn)
+ out ("Luatex version %.2f.%d",
+ status.luatex_version / 100,
+ status.luatex_revision)
+ out ""
+end
+
+
+--- makeshift formatting
+
+local head_adornchars = {
+ [1] = "*", [2] = "=", [3] = "~", [4] = "-", [5] = "·",
+}
+
+local textwidth = 80
+local wd_leftcolumn = math.floor(textwidth * .25)
+local key_fmt = stringformat([[%%%ds]], wd_leftcolumn)
+local val_fmt = [[%s]]
+local fieldseparator = ":"
+local info_fmt = key_fmt .. fieldseparator .. " " .. val_fmt
+
+local currentdepth = 0
+local counterstack = { } -- counters per level
+local counterformat = "%d"
+
+local format_counter = function (stack)
+ local acc = { }
+ for lvl=1, #stack do
+ acc[#acc+1] = stringformat(counterformat, stack[lvl])
+ end
+ return tableconcat(acc, ".")
+end
+
+local print_heading = function (title, level)
+ local structuredata
+ if currentdepth == level then -- top is current
+ counterstack[#counterstack] = counterstack[#counterstack] + 1
+ elseif currentdepth < level then -- push new
+ counterstack[#counterstack+1] = 1
+ else -- pop
+ local diff = currentdepth - level
+ while diff > 0 do
+ counterstack[#counterstack] = nil
+ diff = diff - 1
+ end
+ counterstack[#counterstack] = counterstack[#counterstack] + 1
+ end
+ currentdepth = level
+
+ texiowrite_nl ""
+ if not level or level > #head_adornchars then
+ level = #head_adornchars
+ end
+ local adornchar = head_adornchars[level]
+
+ local counter = format_counter(counterstack)
+
+ local s = adornchar .. adornchar .. " "
+ .. counter .. " "
+ .. title .. " "
+ texiowrite_nl (s .. stringrep(adornchar, textwidth-utf.len(s)))
+end
+
+local baseindent = " "
+
+--[[doc--
+
+ show_info_items -- Together with show_info_table prints the table returned by
+ fontloader.info(), recursing into nested tables if appropriate (as necessitated
+ by Luatex versions 0.78+ which include the pfminfo table in the result.
+
+--doc]]--
+
+local show_info_table show_info_table = function (t, depth)
+ depth = depth or 0
+ local indent = stringrep (baseindent, depth)
+ local keys = table.sortedkeys (t)
+ for n = 1, #keys do
+ local key = keys [n]
+ local val = t [key]
+ if type (val) == "table" then
+ texiowrite_nl (indent .. stringformat (info_fmt, key, "<table>"))
+ show_info_table (val, depth + 1)
+ else
+ texiowrite_nl (indent .. stringformat (info_fmt, key, val))
+ end
+ end
+end
+
+local show_info_items = function (fontinfo)
+ print_heading (fontinfo.fullname, 1)
+ texiowrite_nl ""
+ show_info_table (fontinfo)
+ texiowrite_nl ""
+end
+
+local p_eol = S"\n\r"^1
+local p_space = S" \t\v"^0
+local p_line = p_space * C((1 - p_eol)^1)^-1
+local p_lines = Ct(p_line * (p_eol^1 * p_line^-1)^0)
+
+local show_fontloader_warnings = function (ws)
+ local nws = #ws
+ print_heading(stringformat(
+ [[the fontloader emitted %d warnings]],
+ nws), 2)
+ texiowrite_nl ""
+ for i=1, nws do
+ local w = ws[i]
+ texiowrite_nl (stringformat("%d:", i))
+ local lines = lpegmatch(p_lines, w)
+ for i=1, #lines do
+ local line = lines[i]
+ texiowrite_nl(" · " .. line)
+ end
+ texiowrite_nl ""
+ end
+end
+
+local p_spacechar = S" \n\r\t\v"
+local p_wordchar = (1 - p_spacechar)
+local p_whitespace = p_spacechar^1
+local p_word = C(p_wordchar^1)
+local p_words = Ct(p_word * (p_whitespace * p_word)^0)
+
+--- string -> int -> string list
+local reflow = function (text, width)
+ local words
+ if type(text) == "string" then
+ words = lpegmatch(p_words, text)
+ if #words < 2 then
+ return { text }
+ end
+ else
+ words = text
+ if #words < 2 then
+ return words
+ end
+ end
+
+ local space = " "
+ local utflen = utf.len
+ local reflowed = { }
+
+ local first = words[1]
+ local linelen = #first
+ local line = { first }
+
+ for i=2, #words do
+ local word = words[i]
+ local lword = utflen(word)
+ linelen = linelen + lword + 1
+ if linelen > width then
+ reflowed[#reflowed+1] = tableconcat(line)
+ linelen = #word
+ line = { word }
+ else
+ line[#line+1] = space
+ line[#line+1] = word
+ end
+ end
+ reflowed[#reflowed+1] = tableconcat(line)
+ return reflowed
+end
+
+--- string -> 'a -> string list
+local print_field = function (key, val)
+ val = tostring(val)
+ local lhs = stringformat(key_fmt, key) .. fieldseparator .. " "
+ local wd_lhs = #lhs
+ local lines = reflow(val, textwidth - wd_lhs)
+
+ texiowrite_nl(lhs)
+ texiowrite(lines[1])
+ if #lines > 1 then
+ local indent = stringrep(" ", wd_lhs)
+ for i=2, #lines do
+ texiowrite_nl(indent)
+ texiowrite (lines[i])
+ end
+ end
+end
+
+local display_names = function (names)
+ print_heading("Font Metadata", 2)
+ for i=1, #names do
+ local lang, namedata = names[i].lang, names[i].names
+ print_heading(stringformat("Language: %s ", i, lang), 3)
+ texiowrite_nl ""
+ if namedata then
+ for field, value in next, namedata do
+ print_field(field, value)
+ end
+ end
+ end
+end
+
+--- see luafflib.c
+local general_fields = {
+ --- second: l -> literal | n -> length | d -> date
+ { "fullname", "l", "font name" },
+ { "version", "l", "font version" },
+ { "creationtime", "d", "creation time" },
+ { "modificationtime", "d", "modification time" },
+ { "subfonts", "n", "number of subfonts" },
+ { "glyphcnt", "l", "number of glyphs" },
+ { "weight", "l", "weight indicator" },
+ { "design_size", "l", "design size" },
+ { "design_range_bottom", "l", "design size min" },
+ { "design_range_top", "l", "design size max" },
+ { "fontstyle_id", "l", "font style id" },
+ { "fontstyle_name", "S", "font style name" },
+ { "strokewidth", "l", "stroke width" },
+ { "units_per_em", "l", "units per em" },
+ { "ascent", "l", "ascender height" },
+ { "descent", "l", "descender height" },
+ { "comments", "l", "comments" },
+ { "os2_version", "l", "os2 version" },
+ { "sfd_version", "l", "sfd version" },
+}
+
+local display_general = function (fullinfo)
+ texiowrite_nl ""
+ print_heading("General Information", 2)
+ texiowrite_nl ""
+ for i=1, #general_fields do
+ local field = general_fields[i]
+ local key, mode, desc = unpack(field)
+ local val
+ if mode == "l" then
+ val = fullinfo[key]
+ elseif mode == "S" then --- style names table
+ local data = fullinfo[key]
+ if type (data) == "table" then
+ if #data > 0 then
+ for n = 1, #data do
+ local nth = data[n]
+ if nth.lang == 1033 then
+ val = nth.name
+ goto found
+ end
+ end
+ val = next (data).name
+ else
+ val = ""
+ end
+ ::found::
+ else
+ val = data
+ end
+ elseif mode == "n" then
+ local v = fullinfo[key]
+ if v then
+ val = #fullinfo[key]
+ end
+ elseif mode == "d" then
+ if ostype == "unix" then
+ val = osdate("%F %T", fullinfo[key])
+ else
+ --- the MS compiler doesn’t support C99, so
+ --- strftime is missing some functionality;
+ --- see loslib.c for details.
+ val = osdate("%Y-%m-d %H:%M:%S", fullinfo[key])
+ end
+ end
+ if not val then
+ val = "<none>"
+ end
+ print_field(desc, val)
+ end
+end
+
+local print_features = function (features)
+ for tag, data in next, features do
+ print_heading(tag, 4)
+ for script, languages in next, data do
+ local field = stringformat(key_fmt, script).. fieldseparator .. " "
+ local wd_field = #field
+ local lines = reflow(languages.list, textwidth - wd_field)
+ local indent = stringrep(" ", wd_field)
+ texiowrite_nl(field)
+ texiowrite(lines[1])
+ if #lines > 1 then
+ for i=1, #lines do
+ texiowrite_nl(indent .. lines[i])
+ end
+ end
+ end
+ end
+end
+
+local extract_feature_info = function (set)
+ local collected = { }
+ for i=1, #set do
+ local features = set[i].features
+ if features then
+ for j=1, #features do
+ local feature = features[j]
+ local scripts = feature.scripts
+ local tagname = stringlower(feature.tag)
+ local entry = collected[tagname] or { }
+
+ for k=1, #scripts do
+ local script = scripts[k]
+ local scriptname = stringlower(script.script)
+ local c_script = entry[scriptname] or {
+ list = { },
+ set = { },
+ }
+ local list, set = c_script.list, c_script.set
+
+ for l=1, #script.langs do
+ local langname = stringlower(script.langs[l])
+ if not set[langname] then
+ list[#list+1] = langname
+ set[langname] = true
+ end
+ end
+ entry[scriptname] = c_script
+ end
+ collected[tagname] = entry
+ end
+ end
+ end
+ return collected
+end
+
+local display_feature_set = function (set)
+ local collected = extract_feature_info(set)
+ print_features(collected)
+end
+
+local display_features = function (gsub, gpos)
+ texiowrite_nl ""
+
+ if gsub or gpos then
+ print_heading("Features", 2)
+
+ if gsub then
+ print_heading("GSUB Features", 3)
+ display_feature_set(gsub)
+ end
+
+ if gpos then
+ print_heading("GPOS Features", 3)
+ display_feature_set(gpos)
+ end
+ end
+end
+
+local show_full_info = function (path, subfont, warnings)
+ local rawinfo, warn = fontloader.open(path, subfont)
+ if warnings then
+ show_fontloader_warnings(warn)
+ end
+ if not rawinfo then
+ texiowrite_nl(stringformat([[cannot open font %s]], path))
+ return
+ end
+ local fontdata = { }
+ local fullinfo = fontloader.to_table(rawinfo)
+ local fields = fontloader.fields(rawinfo)
+ fontloader.close(rawinfo)
+ display_names(fullinfo.names)
+ display_general(fullinfo)
+ display_features(fullinfo.gsub, fullinfo.gpos)
+end
+
+--- Subfonts returned by fontloader.info() do not correspond
+--- to the actual indices required by fontloader.open(), so
+--- we try and locate the correct one by matching the request
+--- against the full name.
+
+local subfont_by_name
+subfont_by_name = function (lst, askedname, n)
+ if not n then
+ return subfont_by_name (lst, askedname, 1)
+ end
+
+ local font = lst[n]
+ if font then
+ if sanitize_fontname (font.fullname) == askedname then
+ return font
+ end
+ return subfont_by_name (lst, askedname, n+1)
+ end
+ return false
+end
+
+--[[doc--
+The font info knows two levels of detail:
+
+ a) basic information returned by fontloader.info(); and
+ b) detailed information that is a subset of the font table
+ returned by fontloader.open().
+--doc]]--
+
+local show_font_info = function (basename, askedname, detail, warnings)
+ local filenames = names.data().files
+ local index = filenames.base[basename]
+ local fullname = filenames.full[index]
+ askedname = sanitize_fontname (askedname)
+ if not fullname then -- texmf
+ fullname = resolvers.findfile(basename)
+ end
+ if fullname then
+ local shortinfo = fontloader.info(fullname)
+ local nfonts = #shortinfo
+ if nfonts > 0 then -- true type collection
+ local subfont
+ if askedname then
+ report (true, 1, "resolve",
+ [[%s is part of the font collection %s]],
+ askedname, basename)
+ subfont = subfont_by_name(shortinfo, askedname)
+ end
+ if subfont then
+ show_info_items(subfont)
+ if detail == true then
+ show_full_info(fullname, subfont, warnings)
+ end
+ else -- list all subfonts
+ report (true, 1, "resolve",
+ [[%s is a font collection]], basename)
+ for subfont = 1, nfonts do
+ report (true, 1, "resolve",
+ [[Showing info for font no. %d]], n)
+ show_info_items(shortinfo[subfont])
+ if detail == true then
+ show_full_info(fullname, subfont, warnings)
+ end
+ end
+ end
+ else
+ show_info_items(shortinfo)
+ if detail == true then
+ show_full_info(fullname, subfont, warnings)
+ end
+ end
+ else
+ report (true, 1, "resolve", "Font %s not found", filename)
+ end
+end
+
+--[[--
+Running the scripts triggers one or more actions that have to be
+executed in the correct order. To avoid duplication we track them in a
+set.
+--]]--
+
+local action_sequence = {
+ "loglevel", "help", "version", "diagnose",
+ "blacklist", "cache", "flush", "generate",
+ "list", "query",
+}
+
+local action_pending = tabletohash(action_sequence, false)
+
+action_pending.loglevel = true --- always set the loglevel
+action_pending.generate = false --- this is the default action
+
+local actions = { } --- (jobspec -> (bool * bool)) list
+
+actions.loglevel = function (job)
+ log.set_loglevel(job.log_level)
+ report ("info", 3, "util", "Setting log level", "%d", job.log_level)
+ report ("log", 2, "util", "Lua=%q", _VERSION)
+ return true, true
+end
+
+actions.version = function (job)
+ version_msg()
+ return true, false
+end
+
+actions.help = function (job)
+ help_msg (job.help_version or "luaotfload-tool")
+ return true, false
+end
+
+actions.blacklist = function (job)
+ names.read_blacklist()
+ local n = 0
+ for n, entry in next, table.sortedkeys(names.blacklist) do
+ iowrite (stringformat("(%d %s)\n", n, entry))
+ end
+ return true, false
+end
+
+actions.generate = function (job)
+ local fontnames, savedname
+ fontnames = names.update(fontnames, job.force_reload, job.dry_run)
+ report ("info", 2, "db", "Fonts in the database: %i", #fontnames.mappings)
+ if names.data() then
+ return true, true
+ end
+ return false, false
+end
+
+actions.flush = function (job)
+ local success, lookups = names.flush_lookup_cache()
+ if success then
+ local success = names.save_lookups()
+ if success then
+ report ("info", 2, "cache", "Lookup cache emptied")
+ return true, true
+ end
+ end
+ return false, false
+end
+
+local cache_directives = {
+ ["purge"] = names.purge_cache,
+ ["erase"] = names.erase_cache,
+ ["show"] = names.show_cache,
+}
+
+actions.cache = function (job)
+ local directive = cache_directives[job.cache]
+ if not directive or type(directive) ~= "function" then
+ report ("info", 2, "cache",
+ "Invalid font cache directive %s.", job.cache)
+ return false, false
+ end
+ if directive() then
+ return true, true
+ end
+ return false, false
+end
+
+actions.query = function (job)
+
+ require "luaotfload-features"
+
+ local query = job.query
+
+ local tmpspec = {
+ name = query,
+ lookup = "name",
+ specification = query,
+ optsize = 0,
+ features = { },
+ }
+
+ tmpspec = names.handle_request (tmpspec)
+
+ if not tmpspec.size then
+ tmpspec.size = 655360 --- assume 10pt
+ end
+
+ local foundname, subfont, success
+
+ if tmpspec.lookup == "name"
+ or tmpspec.lookup == "anon" --- not *exactly* as resolvers.anon
+ then
+ foundname, subfont = names.resolve_name (tmpspec)
+ if foundname then
+ foundname, _, success = names.crude_file_lookup (foundname)
+ end
+ elseif tmpspec.lookup == "file" then
+ foundname, _, success =
+ names.crude_file_lookup (tmpspec.name)
+ end
+
+ if success then
+ report (false, 0, "resolve", "Font %q found!", query)
+ if subfont then
+ report (false, 0, "resolve",
+ "Resolved file name %q, subfont nr. %q",
+ foundname, subfont)
+ else
+ report (false, 0, "resolve",
+ "Resolved file name %q", foundname)
+ end
+ if job.show_info then
+ show_font_info (foundname, query, job.full_info, job.warnings)
+ iowrite "\n"
+ end
+ else
+ report (false, 0, "resolve", "Cannot find %q in index.", query)
+ report (false, 0, "resolve",
+ "Hint: use the --fuzzy option to display suggestions.",
+ query)
+ if job.fuzzy == true then
+ report (false, 0, "resolve",
+ "Looking for close matches, this may take a while ...")
+ local _success = names.find_closest(query, job.fuzzy_limit)
+ end
+ end
+ return true, true
+end
+
+--- --list=<criterion>
+--- --list=<criterion>:<value>
+---
+--- --list=<criterion> --fields=<f1>,<f2>,<f3>,...<fn>
+
+local get_fields get_fields = function (entry, fields, acc, n)
+ if not acc then
+ return get_fields (entry, fields, { }, 1)
+ end
+
+ local field = fields [n]
+ if field then
+ local chain = stringsplit (field, "->")
+ local tmp = entry
+ for i = 1, #chain - 1 do
+ tmp = tmp [chain [i]]
+ if not tmp then
+ --- invalid field
+ break
+ end
+ end
+ if tmp then
+ local value = tmp [chain [#chain]]
+ acc[#acc+1] = value or false
+ else
+ acc[#acc+1] = false
+ end
+ return get_fields (entry, fields, acc, n+1)
+ end
+ return acc
+end
+
+local separator = "\t" --- could be “,” for csv
+
+local format_fields format_fields = function (fields, acc, n)
+ if not acc then
+ return format_fields(fields, { }, 1)
+ end
+
+ local field = fields[n]
+ if field ~= nil then
+ if field == false then
+ acc[#acc+1] = "<none>"
+ else
+ acc[#acc+1] = tostring(field)
+ end
+ return format_fields(fields, acc, n+1)
+ end
+ return tableconcat(acc, separator)
+end
+
+local set_primary_field
+set_primary_field = function (fields, addme, acc, n)
+ if not acc then
+ return set_primary_field(fields, addme, { addme }, 1)
+ end
+
+ local field = fields[n]
+ if field then
+ if field ~= addme then
+ acc[#acc+1] = field
+ end
+ return set_primary_field(fields, addme, acc, n+1)
+ end
+ return acc
+end
+
+local splitcomma = luaotfload.parsers.splitcomma
+
+actions.list = function (job)
+ local criterion = job.criterion
+ local asked_fields = job.asked_fields
+ local name_index = names.data ()
+
+ if asked_fields then
+ asked_fields = lpegmatch(splitcomma, asked_fields)
+ end
+
+ if not asked_fields then
+ --- some defaults
+ asked_fields = { "plainname", "version", }
+ end
+
+ if not name_index then
+ name_index = names.load()
+ end
+
+ local mappings = name_index.mappings
+ local nmappings = #mappings
+
+ if criterion == "*" then
+ report (false, 1, "list", "All %d entries", nmappings)
+ for i=1, nmappings do
+ local entry = mappings[i]
+ local fields = get_fields(entry, asked_fields)
+ --- we could collect these instead ...
+ local formatted = format_fields(fields)
+ texiowrite_nl(formatted)
+ end
+
+ else
+ criterion = stringexplode(criterion, ":") --> { field, value }
+ local asked_value = criterion[2]
+ criterion = criterion[1]
+ asked_fields = set_primary_field(asked_fields, criterion)
+
+ report (false, 1, "list", "By %s", criterion)
+
+ --- firstly, build a list of fonts to operate on
+ local targets = { }
+ if asked_value then --- only those whose value matches
+ report (false, 2, "list", "Restricting to value %s", asked_value)
+ for i=1, nmappings do
+ local entry = mappings[i]
+ if entry[criterion]
+ and tostring(entry[criterion]) == asked_value
+ then
+ targets[#targets+1] = entry
+ end
+ end
+
+ else --- whichever have the field, sorted
+ local categories, by_category = { }, { }
+ for i=1, nmappings do
+ local entry = mappings[i]
+ local tmp = entry
+ local chain = stringsplit (criterion, "->")
+ for i = 1, #chain - 1 do
+ tmp = tmp [chain [i]]
+ if not tmp then
+ break
+ end
+ end
+ local value = tmp and tmp [chain [#chain]] or "<none>"
+ if value then
+ --value = tostring(value)
+ local entries = by_category[value]
+ if not entries then
+ entries = { entry }
+ categories[#categories+1] = value
+ else
+ entries[#entries+1] = entry
+ end
+ by_category[value] = entries
+ end
+ end
+ table.sort(categories)
+
+ for i=1, #categories do
+ local entries = by_category[categories[i]]
+ for j=1, #entries do
+ targets[#targets+1] = entries[j]
+ end
+ end
+ end
+ local ntargets = #targets
+ report (false, 2, "list", "%d entries", ntargets)
+
+ --- now, output the collection
+ for i=1, ntargets do
+ local entry = targets[i]
+ local fields = get_fields(entry, asked_fields)
+ local formatted = format_fields(fields)
+ texiowrite_nl(formatted)
+ end
+ end
+
+ texiowrite_nl ""
+
+ return true, true
+end
+
+actions.diagnose = function (job)
+ --- diagnostics are loaded on demand
+ local diagnose = require "luaotfload-diagnostics.lua"
+ return diagnose (job)
+end
+
+--- stuff to be carried out prior to exit
+
+local finalizers = { }
+
+--- returns false if at least one of the actions failed, mainly
+--- for closing io channels
+local finalize = function ()
+ local success = true
+ for _, fun in next, finalizers do
+ if type (fun) == "function" then
+ if fun () == false then success = false end
+ end
+ end
+ return success
+end
+
+--[[--
+Command-line processing.
+luaotfload-tool relies on the script alt_getopt to process argv and
+analyzes its output.
+
+TODO with extended lualibs we have the functionality from the
+environment.* namespace that could eliminate the dependency on
+alt_getopt.
+--]]--
+
+local process_cmdline = function ( ) -- unit -> jobspec
+ local result = { -- jobspec
+ force_reload = nil,
+ full_info = false,
+ warnings = false,
+ criterion = "",
+ query = "",
+ log_level = 0, --- 2 is approx. the old behavior
+ }
+
+ local long_options = {
+ cache = 1,
+ ["no-compress"] = "c",
+ diagnose = 1,
+ ["dry-run"] = "D",
+ ["flush-lookups"] = "l",
+ fields = 1,
+ find = 1,
+ force = "f",
+ formats = 1,
+ fuzzy = "F",
+ help = "h",
+ info = "i",
+ inspect = "I",
+ limit = 1,
+ list = 1,
+ log = 1,
+ ["max-fonts"] = 1,
+ ["no-reload"] = "n",
+ ["no-strip"] = 0,
+ ["skip-read"] = "R",
+ ["prefer-texmf"] = "p",
+ quiet = "q",
+ ["show-blacklist"] = "b",
+ stats = "S",
+ update = "u",
+ verbose = 1,
+ version = "V",
+ warnings = "w",
+ }
+
+ local short_options = "bcDfFiIlnpqRSuvVhw"
+
+ local options, _, optarg =
+ alt_getopt.get_ordered_opts (arg, short_options, long_options)
+
+ local nopts = #options
+ for n=1, nopts do
+ local v = options[n]
+ if v == "q" then
+ result.log_level = 0
+ elseif v == "u" then
+ action_pending["generate"] = true
+ elseif v == "v" then
+ if result.log_level > 0 then
+ result.log_level = result.log_level + 1
+ else
+ result.log_level = 1
+ end
+ elseif v == "V" then
+ action_pending["version"] = true
+ elseif v == "h" then
+ action_pending["help"] = true
+ elseif v == "f" then
+ result.update = true
+ result.force_reload = 1
+ elseif v == "verbose" then
+ local lvl = optarg[n]
+ if lvl then
+ lvl = tonumber(lvl)
+ result.log_level = lvl
+ if lvl > 2 then
+ result.warnings = true
+ end
+ end
+ elseif v == "w" then
+ result.warnings = true
+ elseif v == "log" then
+ local str = optarg[n]
+ if str then
+ finalizers = log.set_logout(str, finalizers)
+ end
+ elseif v == "find" then
+ action_pending["query"] = true
+ result.query = optarg[n]
+ elseif v == "F" then
+ result.fuzzy = true
+ elseif v == "limit" then
+ local lim = optarg[n]
+ if lim then
+ result.fuzzy_limit = tonumber(lim)
+ end
+ elseif v == "i" then
+ result.show_info = true
+ elseif v == "I" then
+ result.show_info = true
+ result.full_info = true
+ elseif v == "l" then
+ action_pending["flush"] = true
+ elseif v == "list" then
+ action_pending["list"] = true
+ result.criterion = optarg[n]
+ elseif v == "fields" then
+ result.asked_fields = optarg[n]
+ elseif v == "cache" then
+ action_pending["cache"] = true
+ result.cache = optarg[n]
+ elseif v == "D" then
+ result.dry_run = true
+ elseif v == "p" then
+ names.set_location_precedence {
+ "local", "texmf", "system"
+ }
+ elseif v == "b" then
+ action_pending["blacklist"] = true
+ elseif v == "diagnose" then
+ action_pending["diagnose"] = true
+ result.asked_diagnostics = optarg[n]
+ elseif v == "formats" then
+ names.set_font_filter (optarg[n])
+ elseif v == "n" then
+ luaotfloadconfig.update_live = false
+ elseif v == "S" then
+ luaotfloadconfig.statistics = true
+ elseif v == "R" then
+ --- dev only, undocumented
+ luaotfloadconfig.skip_read = true
+ elseif v == "c" then
+ luaotfloadconfig.compress = false
+ elseif v == "no-strip" then
+ luaotfloadconfig.strip = false
+ elseif v == "max-fonts" then
+ local n = optarg[n]
+ if n then
+ n = tonumber(n)
+ if n and n > 0 then
+ luaotfloadconfig.max_fonts = n
+ end
+ end
+ end
+ end
+
+ if nopts == 0 then
+ action_pending["help"] = true
+ result.help_version = "short"
+ end
+ return result
+end
+
+local main = function ( ) -- unit -> int
+ local retval = 0
+ local job = process_cmdline()
+
+-- inspect(action_pending)
+-- inspect(job)
+
+ for i=1, #action_sequence do
+ local actionname = action_sequence[i]
+ local exit = false
+ if action_pending[actionname] then
+ report ("log", 3, "util", "Preparing for task", "%s", actionname)
+
+ local action = actions[actionname]
+ local success, continue = action(job)
+
+ if not success then
+ report (false, 0, "util",
+ "Could not finish task", "%s", actionname)
+ retval = -1
+ exit = true
+ elseif not continue then
+ report (false, 3, "util",
+ "Task completed, exiting", "%s", actionname)
+ exit = true
+ else
+ report (false, 3, "util",
+ "Task completed successfully", "%s", actionname)
+ end
+ end
+ if exit then break end
+ end
+
+ if finalize () == false then
+ retval = -1
+ end
+
+ --texiowrite_nl""
+ return retval
+end
+
+return main()
+
+-- vim:tw=71:sw=4:ts=4:expandtab