You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1033 lines
30 KiB
1033 lines
30 KiB
-------------------------------------------------------------------------------
|
|
-- FILE: luaotfload-configuration.lua
|
|
-- DESCRIPTION: part of luaotfload / luaotfload-tool / config file reader
|
|
-- AUTHOR: Philipp Gesang, <phg@phi-gamma.net>
|
|
-- AUTHOR: Dohyun Kim <nomosnomos@gmail.com>
|
|
-------------------------------------------------------------------------------
|
|
|
|
local ProvidesLuaModule = {
|
|
name = "luaotfload-configuration",
|
|
version = "3.14", --TAGVERSION
|
|
date = "2020-05-06", --TAGDATE
|
|
description = "luaotfload submodule / config file reader",
|
|
license = "GPL v2.0"
|
|
}
|
|
|
|
if luatexbase and luatexbase.provides_module then
|
|
luatexbase.provides_module (ProvidesLuaModule)
|
|
end
|
|
|
|
------------------------------
|
|
|
|
local status_file = "luaotfload-status"
|
|
local luaotfloadstatus = require (status_file)
|
|
|
|
local string = string
|
|
local stringfind = string.find
|
|
local stringformat = string.format
|
|
local stringstrip = string.strip
|
|
local stringsub = string.sub
|
|
|
|
local tableappend = table.append
|
|
local tableconcat = table.concat
|
|
local tablecopy = table.copy
|
|
local table = table
|
|
local tabletohash = table.tohash
|
|
|
|
local math = math
|
|
local mathfloor = math.floor
|
|
|
|
local io = io
|
|
local ioloaddata = io.loaddata
|
|
local iopopen = io.popen
|
|
local iowrite = io.write
|
|
|
|
local os = os
|
|
local osdate = os.date
|
|
local osgetenv = os.getenv
|
|
|
|
local lpeg = require "lpeg"
|
|
local lpegmatch = lpeg.match
|
|
local commasplitter = lpeg.splitat ","
|
|
local equalssplitter = lpeg.splitat "="
|
|
|
|
local kpseexpand_path = kpse.expand_path
|
|
|
|
local lfs = lfs
|
|
local lfsisfile = lfs.isfile
|
|
local lfsisdir = lfs.isdir
|
|
|
|
local file = file
|
|
local filejoin = file.join
|
|
local filereplacesuffix = file.replacesuffix
|
|
|
|
local logreport = print -- overloaded later
|
|
local getwritablepath = luaotfload.fontloader.caches.getwritablepath
|
|
|
|
|
|
local config_parser -- set later during init
|
|
local stripslashes -- set later during init
|
|
|
|
-------------------------------------------------------------------------------
|
|
--- SETTINGS
|
|
-------------------------------------------------------------------------------
|
|
|
|
local path_t = 0
|
|
local kpse_t = 1
|
|
|
|
local val_home = kpseexpand_path "~"
|
|
local val_xdg_config_home = kpseexpand_path "$XDG_CONFIG_HOME"
|
|
|
|
if val_xdg_config_home == "" then val_xdg_config_home = "~/.config" end
|
|
|
|
local config_paths = {
|
|
--- needs adapting for those other OS
|
|
{ path_t, "./luaotfload.conf" },
|
|
{ path_t, "./luaotfloadrc" },
|
|
{ path_t, filejoin (val_xdg_config_home, "luaotfload/luaotfload.conf") },
|
|
{ path_t, filejoin (val_xdg_config_home, "luaotfload/luaotfloadrc") },
|
|
{ path_t, filejoin (val_home, ".luaotfloadrc") },
|
|
{ kpse_t, "luaotfloadrc" },
|
|
{ kpse_t, "luaotfload.conf" },
|
|
}
|
|
|
|
local valid_formats = tabletohash {
|
|
"otf", "ttc", "ttf", "afm", "pfb"
|
|
}
|
|
|
|
local default_anon_sequence = {
|
|
"tex", "path", "name"
|
|
}
|
|
|
|
local valid_resolvers = tabletohash {
|
|
"tex", "path", "name", "file", "my"
|
|
}
|
|
|
|
local feature_presets = {
|
|
arab = tabletohash {
|
|
"ccmp", "locl", "isol", "fina", "fin2",
|
|
"fin3", "medi", "med2", "init", "rlig",
|
|
"calt", "liga", "cswh", "mset", "curs",
|
|
"kern", "mark", "mkmk",
|
|
},
|
|
deva = tabletohash {
|
|
"ccmp", "locl", "init", "nukt", "akhn",
|
|
"rphf", "blwf", "half", "pstf", "vatu",
|
|
"pres", "blws", "abvs", "psts", "haln",
|
|
"calt", "blwm", "abvm", "dist", "kern",
|
|
"mark", "mkmk",
|
|
},
|
|
khmr = tabletohash {
|
|
"ccmp", "locl", "pref", "blwf", "abvf",
|
|
"pstf", "pres", "blws", "abvs", "psts",
|
|
"clig", "calt", "blwm", "abvm", "dist",
|
|
"kern", "mark", "mkmk",
|
|
},
|
|
thai = tabletohash {
|
|
"ccmp", "locl", "liga", "kern", "mark",
|
|
"mkmk",
|
|
},
|
|
}
|
|
|
|
--[[doc--
|
|
|
|
We allow loading of arbitrary fontloaders. Nevertheless we maintain a
|
|
list of the “official” ones shipped with Luaotfload so we can emit a
|
|
different log message.
|
|
|
|
--doc]]--
|
|
|
|
local function default_fontloader ()
|
|
return luaotfloadstatus and luaotfloadstatus.notes.loader or "reference"
|
|
end
|
|
|
|
local registered_loaders = {
|
|
default = default_fontloader (),
|
|
reference = "reference",
|
|
unpackaged = "unpackaged",
|
|
context = "context",
|
|
}
|
|
|
|
local function pick_fontloader (s)
|
|
local ldr = registered_loaders[s]
|
|
if ldr ~= nil and type (ldr) == "string" then
|
|
logreport ("log", 2, "conf", "Using predefined fontloader %q.", ldr)
|
|
return ldr
|
|
end
|
|
local idx = stringfind (s, ":")
|
|
if idx and idx > 2 then
|
|
if stringsub (s, 1, idx - 1) == "context" then
|
|
local pth = stringsub (s, idx + 1)
|
|
pth = stringstrip (pth)
|
|
logreport ("log", 2, "conf", "Context base path specified at %q.", pth)
|
|
if lfsisdir (pth) then
|
|
logreport ("log", 5, "conf", "Context base path exists at %q.", pth)
|
|
return pth
|
|
end
|
|
pth = kpseexpand_path (pth)
|
|
if lfsisdir (pth) then
|
|
logreport ("log", 5, "conf", "Context base path exists at %q.", pth)
|
|
return pth
|
|
end
|
|
logreport ("both", 0, "conf", "Context base path not found at %q.", pth)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--[[doc--
|
|
|
|
The ``post_linebreak_filter`` has been made the default callback for
|
|
hooking the colorizer into. This helps with the linebreaking whose
|
|
inserted hyphens would remain unaffected by the coloring otherwise.
|
|
|
|
http://tex.stackexchange.com/q/238539/14066
|
|
|
|
--doc]]--
|
|
|
|
local permissible_color_callbacks = {
|
|
default = "post_linebreak_filter",
|
|
pre_linebreak_filter = "pre_linebreak_filter",
|
|
post_linebreak_filter = "post_linebreak_filter",
|
|
pre_output_filter = "pre_output_filter",
|
|
}
|
|
|
|
|
|
-------------------------------------------------------------------------------
|
|
--- DEFAULTS
|
|
-------------------------------------------------------------------------------
|
|
|
|
local default_config = {
|
|
db = {
|
|
formats = "otf,ttf,ttc",
|
|
scan_local = false,
|
|
skip_read = false,
|
|
strip = true,
|
|
update_live = true,
|
|
compress = true,
|
|
max_fonts = 2^51,
|
|
designsize_dimen= "bp",
|
|
},
|
|
run = {
|
|
anon_sequence = default_anon_sequence,
|
|
resolver = "cached",
|
|
definer = "patch",
|
|
log_level = 0,
|
|
color_callback = "post_linebreak_filter",
|
|
fontloader = default_fontloader (),
|
|
},
|
|
misc = {
|
|
bisect = false,
|
|
version = luaotfload.version,
|
|
statistics = false,
|
|
termwidth = nil,
|
|
},
|
|
paths = {
|
|
names_dir = "names",
|
|
cache_dir = "fonts",
|
|
index_file = "luaotfload-names.lua",
|
|
lookups_file = "luaotfload-lookup-cache.lua",
|
|
lookup_path_lua = nil,
|
|
lookup_path_luc = nil,
|
|
index_path_lua = nil,
|
|
index_path_luc = nil,
|
|
},
|
|
default_features = {
|
|
global = { mode = "node" },
|
|
dflt = tabletohash {
|
|
"ccmp", "locl", "rlig", "liga", "clig",
|
|
"kern", "mark", "mkmk", 'itlc',
|
|
},
|
|
|
|
arab = feature_presets.arab,
|
|
syrc = feature_presets.arab,
|
|
mong = feature_presets.arab,
|
|
nko = feature_presets.arab,
|
|
|
|
deva = feature_presets.deva,
|
|
beng = feature_presets.deva,
|
|
guru = feature_presets.deva,
|
|
gujr = feature_presets.deva,
|
|
orya = feature_presets.deva,
|
|
taml = feature_presets.deva,
|
|
telu = feature_presets.deva,
|
|
knda = feature_presets.deva,
|
|
mlym = feature_presets.deva,
|
|
sinh = feature_presets.deva,
|
|
|
|
khmr = feature_presets.khmr,
|
|
tibt = feature_presets.khmr,
|
|
thai = feature_presets.thai,
|
|
lao = feature_presets.thai,
|
|
|
|
hang = tabletohash { "ccmp", "ljmo", "vjmo", "tjmo", },
|
|
},
|
|
}
|
|
|
|
-------------------------------------------------------------------------------
|
|
--- RECONFIGURATION TASKS
|
|
-------------------------------------------------------------------------------
|
|
|
|
--[[doc--
|
|
|
|
Procedures to be executed in order to put the new configuration into effect.
|
|
|
|
--doc]]--
|
|
|
|
local reconf_tasks = nil
|
|
|
|
local min_terminal_width = 40
|
|
|
|
--- The “termwidth” value is only considered when printing
|
|
--- short status messages, e.g. when building the database
|
|
--- online.
|
|
local function check_termwidth ()
|
|
if config.luaotfload.misc.termwidth == nil 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
|
|
logreport ("log", 2, "db", "tput returned non-number.")
|
|
end
|
|
else
|
|
logreport ("log", 2, "db", "Shell escape disabled or tput executable missing.")
|
|
logreport ("log", 2, "db", "Assuming 79 cols terminal width.")
|
|
end
|
|
end
|
|
config.luaotfload.misc.termwidth = tw
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function set_font_filter ()
|
|
local names = fonts.names
|
|
if names and names.set_font_filter then
|
|
local formats = config.luaotfload.db.formats
|
|
if not formats or formats == "" then
|
|
formats = default_config.db.formats
|
|
end
|
|
names.set_font_filter (formats)
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function set_size_dimension ()
|
|
local names = fonts.names
|
|
if names and names.set_size_dimension then
|
|
local dim = config.luaotfload.db.designsize_dimen
|
|
if not dim or dim == "" then
|
|
dim = default_config.db.designsize_dimen
|
|
end
|
|
names.set_size_dimension (dim)
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function set_name_resolver ()
|
|
local names = fonts.names
|
|
if names and names.resolve_cached then
|
|
--- replace the resolver from luatex-fonts
|
|
if config.luaotfload.db.resolver == "cached" then
|
|
logreport ("both", 2, "cache", "Caching of name: lookups active.")
|
|
names.resolvespec = fonts.names.lookup_font_name_cached
|
|
else
|
|
names.resolvespec = fonts.names.lookup_font_name
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function set_loglevel ()
|
|
if luaotfload then
|
|
luaotfload.log.set_loglevel (config.luaotfload.run.log_level)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function build_cache_paths ()
|
|
local paths = config.luaotfload.paths
|
|
local prefix = getwritablepath (paths.names_dir, "")
|
|
|
|
if not prefix then
|
|
luaotfload.error ("Impossible to find a suitable writeable cache...")
|
|
return false
|
|
end
|
|
|
|
prefix = lpegmatch (stripslashes, prefix)
|
|
logreport ("log", 0, "conf", "Root cache directory is %q.", prefix)
|
|
|
|
local index_file = filejoin (prefix, paths.index_file)
|
|
local lookups_file = filejoin (prefix, paths.lookups_file)
|
|
|
|
paths.prefix = prefix
|
|
paths.index_path_lua = filereplacesuffix (index_file, "lua")
|
|
paths.index_path_luc = filereplacesuffix (index_file, "luc")
|
|
paths.lookup_path_lua = filereplacesuffix (lookups_file, "lua")
|
|
paths.lookup_path_luc = filereplacesuffix (lookups_file, "luc")
|
|
return true
|
|
end
|
|
|
|
|
|
local function set_default_features ()
|
|
local default_features = config.luaotfload.default_features
|
|
luaotfload.features = luaotfload.features or {
|
|
global = { },
|
|
defaults = { },
|
|
}
|
|
local current_features = luaotfload.features
|
|
for var, val in next, default_features do
|
|
if var == "global" then
|
|
current_features.global = val
|
|
else
|
|
current_features.defaults[var] = val
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
reconf_tasks = {
|
|
{ "Set the log level" , set_loglevel },
|
|
{ "Build cache paths" , build_cache_paths },
|
|
{ "Check terminal dimensions" , check_termwidth },
|
|
{ "Set the font filter" , set_font_filter },
|
|
{ "Set design size dimension" , set_size_dimension },
|
|
{ "Install font name resolver", set_name_resolver },
|
|
{ "Set default features" , set_default_features },
|
|
}
|
|
|
|
-------------------------------------------------------------------------------
|
|
--- OPTION SPECIFICATION
|
|
-------------------------------------------------------------------------------
|
|
|
|
local string_t = "string"
|
|
local table_t = "table"
|
|
local number_t = "number"
|
|
local boolean_t = "boolean"
|
|
local function_t = "function"
|
|
|
|
local function tointeger (n)
|
|
n = tonumber (n)
|
|
if n then
|
|
return mathfloor (n + 0.5)
|
|
end
|
|
end
|
|
|
|
local function toarray (s)
|
|
local fields = { lpegmatch (commasplitter, s) }
|
|
local ret = { }
|
|
for i = 1, #fields do
|
|
local field = stringstrip (fields[i])
|
|
if field and field ~= "" then
|
|
ret[#ret + 1] = field
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
local function tohash (s)
|
|
local result = { }
|
|
local fields = toarray (s)
|
|
for _, field in next, fields do
|
|
local var, val
|
|
if stringfind (field, "=") then
|
|
local tmp
|
|
var, tmp = lpegmatch (equalssplitter, field)
|
|
if tmp == "true" or tmp == "yes" then val = true else val = tmp end
|
|
else
|
|
var, val = field, true
|
|
end
|
|
result[var] = val
|
|
end
|
|
return result
|
|
end
|
|
|
|
local option_spec = {
|
|
db = {
|
|
formats = {
|
|
in_t = string_t,
|
|
out_t = string_t,
|
|
transform = function (f)
|
|
local fields = toarray (f)
|
|
|
|
--- check validity
|
|
if not fields then
|
|
logreport ("both", 0, "conf",
|
|
"Expected list of identifiers, got %q.", f)
|
|
return nil
|
|
end
|
|
|
|
--- strip dupes
|
|
local known = { }
|
|
local result = { }
|
|
for i = 1, #fields do
|
|
local field = fields[i]
|
|
if known[field] ~= true then
|
|
--- yet unknown, tag as seen
|
|
known[field] = true
|
|
--- include in output if valid
|
|
if valid_formats[field] == true then
|
|
result[#result + 1] = field
|
|
else
|
|
logreport ("both", 4, "conf",
|
|
"Invalid font format identifier %q, ignoring.",
|
|
field)
|
|
end
|
|
end
|
|
end
|
|
if #result == 0 then
|
|
--- force defaults
|
|
return nil
|
|
end
|
|
return tableconcat (result, ",")
|
|
end
|
|
},
|
|
scan_local = { in_t = boolean_t, },
|
|
skip_read = { in_t = boolean_t, },
|
|
strip = { in_t = boolean_t, },
|
|
update_live = { in_t = boolean_t, },
|
|
compress = { in_t = boolean_t, },
|
|
max_fonts = {
|
|
in_t = number_t,
|
|
out_t = number_t, --- TODO int_t from 5.3.x on
|
|
transform = tointeger,
|
|
},
|
|
designsize_dimen = {
|
|
in_t = string_t,
|
|
out_t = string_t,
|
|
},
|
|
},
|
|
run = {
|
|
anon_sequence = {
|
|
in_t = string_t,
|
|
out_t = table_t,
|
|
transform = function (s)
|
|
if s ~= "default" then
|
|
local bits = { lpegmatch (commasplitter, s) }
|
|
if next (bits) then
|
|
local seq = { }
|
|
local done = { }
|
|
for i = 1, #bits do
|
|
local bit = bits [i]
|
|
if valid_resolvers [bit] then
|
|
if not done [bit] then
|
|
done [bit] = true
|
|
seq [#seq + 1] = bit
|
|
else
|
|
logreport ("both", 0, "conf",
|
|
"ignoring duplicate resolver %s at position %d \z
|
|
in lookup sequence",
|
|
bit, i)
|
|
end
|
|
else
|
|
logreport ("both", 0, "conf",
|
|
"lookup sequence contains invalid item %s \z
|
|
at position %d.",
|
|
bit, i)
|
|
end
|
|
end
|
|
if next (seq) then
|
|
logreport ("both", 2, "conf",
|
|
"overriding anon lookup sequence %s.",
|
|
tableconcat (seq, ","))
|
|
return seq
|
|
end
|
|
end
|
|
end
|
|
return default_anon_sequence
|
|
end
|
|
},
|
|
live = { in_t = boolean_t, }, --- false for the tool, true for TeX run
|
|
resolver = {
|
|
in_t = string_t,
|
|
out_t = string_t,
|
|
transform = function (r) return r == "normal" and r or "cached" end,
|
|
},
|
|
definer = {
|
|
in_t = string_t,
|
|
out_t = string_t,
|
|
transform = function (d)
|
|
if d == "generic" or d == "patch"
|
|
or d == "info_generic" or d == "info_patch"
|
|
then
|
|
return d
|
|
end
|
|
return "patch"
|
|
end,
|
|
},
|
|
fontloader = {
|
|
in_t = string_t,
|
|
out_t = string_t,
|
|
transform = function (id)
|
|
local ldr = pick_fontloader (id)
|
|
if ldr ~= nil then
|
|
return ldr
|
|
end
|
|
logreport ("log", 0, "conf",
|
|
"Requested fontloader %q not defined, "
|
|
.. "use at your own risk.",
|
|
id)
|
|
return id
|
|
end,
|
|
},
|
|
log_level = {
|
|
in_t = number_t,
|
|
out_t = number_t, --- TODO int_t from 5.3.x on
|
|
transform = tointeger,
|
|
},
|
|
color_callback = {
|
|
in_t = string_t,
|
|
out_t = string_t,
|
|
transform = function (cb_spec)
|
|
--- These are the two that make sense.
|
|
local cb = permissible_color_callbacks[cb_spec]
|
|
if cb then
|
|
logreport ("log", 3, "conf",
|
|
"Using callback %q for font colorization.",
|
|
cb)
|
|
return cb
|
|
end
|
|
logreport ("log", 0, "conf",
|
|
"Requested callback identifier %q invalid, "
|
|
.. "falling back to default.",
|
|
cb_spec)
|
|
return permissible_color_callbacks.default
|
|
end,
|
|
},
|
|
},
|
|
misc = {
|
|
bisect = { in_t = boolean_t, }, --- doesn’t make sense in a config file
|
|
version = { in_t = string_t, },
|
|
statistics = { in_t = boolean_t, },
|
|
termwidth = {
|
|
in_t = number_t,
|
|
out_t = number_t,
|
|
transform = function (w)
|
|
w = tointeger (w)
|
|
if w < min_terminal_width then
|
|
return min_terminal_width
|
|
end
|
|
return w
|
|
end,
|
|
},
|
|
},
|
|
paths = {
|
|
names_dir = { in_t = string_t, },
|
|
cache_dir = { in_t = string_t, },
|
|
index_file = { in_t = string_t, },
|
|
lookups_file = { in_t = string_t, },
|
|
lookup_path_lua = { in_t = string_t, },
|
|
lookup_path_luc = { in_t = string_t, },
|
|
index_path_lua = { in_t = string_t, },
|
|
index_path_luc = { in_t = string_t, },
|
|
},
|
|
default_features = {
|
|
__default = { in_t = string_t, out_t = table_t, transform = tohash, },
|
|
},
|
|
}
|
|
|
|
-------------------------------------------------------------------------------
|
|
--- FORMATTERS
|
|
-------------------------------------------------------------------------------
|
|
|
|
local function commented (str)
|
|
return ";" .. str
|
|
end
|
|
|
|
local underscore_replacer = lpeg.replacer ("_", "-", true)
|
|
|
|
local function dashed (var)
|
|
--- INI spec dictates that dashes are valid in variable names, not
|
|
--- underscores.
|
|
return underscore_replacer (var) or var
|
|
end
|
|
|
|
local indent = " "
|
|
local function format_string (var, val)
|
|
return stringformat (indent .. "%s = %s", var, val)
|
|
end
|
|
|
|
local function format_integer (var, val)
|
|
return stringformat (indent .. "%s = %d", var, val)
|
|
end
|
|
|
|
local function format_boolean (var, val)
|
|
return stringformat (indent .. "%s = %s", var, val == true and "true" or "false")
|
|
end
|
|
|
|
local function format_keyval (var, val)
|
|
local list = { }
|
|
local keys = table.sortedkeys (val)
|
|
for i = 1, #keys do
|
|
local key = keys[i]
|
|
local subval = val[key]
|
|
if subval == true then
|
|
list[#list + 1] = stringformat ("%s", key)
|
|
else
|
|
list[#list + 1] = stringformat ("%s=%s", key, val[key])
|
|
end
|
|
end
|
|
if next (list) then
|
|
return stringformat (indent .. "%s = %s",
|
|
var,
|
|
tableconcat (list, ","))
|
|
end
|
|
end
|
|
|
|
local function format_list (var, val)
|
|
local elts = { }
|
|
for i = 1, #val do elts [i] = val [i] end
|
|
if next (elts) then
|
|
return stringformat (indent .. "%s = %s",
|
|
var, tableconcat (elts, ","))
|
|
end
|
|
end
|
|
|
|
local function format_section (title)
|
|
return stringformat ("[%s]", dashed (title))
|
|
end
|
|
|
|
local conf_header = [==[
|
|
;;-----------------------------------------------------------------------------
|
|
;; Luaotfload Configuration
|
|
;;-----------------------------------------------------------------------------
|
|
;;
|
|
;; This file was generated by luaotfload-tool
|
|
;; on %s. Configuration variables
|
|
;; are documented in the manual to luaotfload.conf(5).
|
|
;;
|
|
;;-----------------------------------------------------------------------------
|
|
|
|
]==]
|
|
|
|
local conf_footer = [==[
|
|
|
|
;; vim:filetype=dosini:expandtab:shiftwidth=2
|
|
]==]
|
|
|
|
--- Each dumpable variable (the ones mentioned in the man page) receives a
|
|
--- formatter that will be used in dumping the variable. Each value receives a
|
|
--- “commented” flag that indicates whether or not the line should be printed
|
|
--- as a comment.
|
|
|
|
local formatters = {
|
|
db = {
|
|
compress = { false, format_boolean },
|
|
designsize_dimen = { false, format_string },
|
|
formats = { false, format_string },
|
|
max_fonts = { false, format_integer },
|
|
scan_local = { false, format_boolean },
|
|
skip_read = { false, format_boolean },
|
|
strip = { false, format_boolean },
|
|
update_live = { false, format_boolean },
|
|
},
|
|
default_features = {
|
|
__default = { true, format_keyval },
|
|
},
|
|
misc = {
|
|
bisect = { false, format_boolean },
|
|
statistics = { false, format_boolean },
|
|
termwidth = { true , format_integer },
|
|
version = { true , format_string },
|
|
},
|
|
paths = {
|
|
cache_dir = { false, format_string },
|
|
names_dir = { false, format_string },
|
|
index_file = { false, format_string },
|
|
lookups_file = { false, format_string },
|
|
},
|
|
run = {
|
|
anon_sequence = { false, format_list },
|
|
color_callback = { false, format_string },
|
|
definer = { false, format_string },
|
|
fontloader = { false, format_string },
|
|
log_level = { false, format_integer },
|
|
resolver = { false, format_string },
|
|
},
|
|
}
|
|
|
|
-------------------------------------------------------------------------------
|
|
--- MAIN FUNCTIONALITY
|
|
-------------------------------------------------------------------------------
|
|
|
|
--[[doc--
|
|
|
|
tilde_expand -- Rudimentary tilde expansion; covers just the “substitute ‘~’
|
|
by the current users’s $HOME” part.
|
|
|
|
--doc]]--
|
|
|
|
local function tilde_expand (p)
|
|
if #p > 2 then
|
|
if stringsub (p, 1, 2) == "~/" then
|
|
local homedir = osgetenv "HOME"
|
|
if homedir and lfsisdir (homedir) then
|
|
p = filejoin (homedir, stringsub (p, 3))
|
|
end
|
|
end
|
|
end
|
|
return p
|
|
end
|
|
|
|
local function resolve_config_path ()
|
|
for i = 1, #config_paths do
|
|
local t, p = unpack (config_paths[i])
|
|
local fullname
|
|
if t == kpse_t then
|
|
fullname = kpse.find_file (p)
|
|
logreport ("both", 6, "conf", "kpse lookup: %s -> %s.", p, fullname)
|
|
elseif t == path_t then
|
|
local expanded = tilde_expand (p)
|
|
if lfsisfile (expanded) then
|
|
fullname = expanded
|
|
end
|
|
logreport ("both", 6, "conf", "path lookup: %s -> %s.", p, fullname)
|
|
end
|
|
if fullname then
|
|
logreport ("both", 3, "conf", "Reading configuration file at %q.", fullname)
|
|
return fullname
|
|
end
|
|
end
|
|
logreport ("both", 2, "conf", "No configuration file found.")
|
|
return false
|
|
end
|
|
|
|
local function add_config_paths (t)
|
|
if not next (t) then
|
|
return
|
|
end
|
|
local result = { }
|
|
for i = 1, #t do
|
|
local path = t[i]
|
|
result[#result + 1] = { path_t, path }
|
|
end
|
|
config_paths = tableappend (result, config_paths)
|
|
end
|
|
|
|
local process_options = function (opts)
|
|
local new = { }
|
|
for i = 1, #opts do
|
|
local section = opts[i]
|
|
local title = section.section.title
|
|
local vars = section.variables
|
|
|
|
if not title then --- trigger warning: arrow code ahead
|
|
logreport ("both", 2, "conf", "Section %d lacks a title; skipping.", i)
|
|
elseif not vars then
|
|
logreport ("both", 2, "conf", "Section %d (%s) lacks a variable section; skipping.", i, title)
|
|
else
|
|
local spec = option_spec[title]
|
|
if not spec then
|
|
logreport ("both", 2, "conf", "Section %d (%s) unknown; skipping.", i, title)
|
|
else
|
|
local newsection = new[title]
|
|
if not newsection then
|
|
newsection = { }
|
|
new[title] = newsection
|
|
end
|
|
|
|
for var, val in next, vars do
|
|
local vspec = spec[var] or spec.__default
|
|
local t_val = type (val)
|
|
if not vspec then
|
|
logreport ("both", 2, "conf",
|
|
"Section %d (%s): invalid configuration variable %q (%q); ignoring.",
|
|
i, title,
|
|
var, tostring (val))
|
|
elseif t_val ~= vspec.in_t then
|
|
logreport ("both", 2, "conf",
|
|
"Section %d (%s): type mismatch of input value %q (%q, %s != %s); ignoring.",
|
|
i, title,
|
|
var, tostring (val), t_val, vspec.in_t)
|
|
else --- type matches
|
|
local transform = vspec.transform
|
|
if transform then
|
|
local dval
|
|
local t_transform = type (transform)
|
|
if t_transform == function_t then
|
|
dval = transform (val)
|
|
elseif t_transform == table_t then
|
|
dval = transform[val]
|
|
end
|
|
if dval then
|
|
local out_t = vspec.out_t
|
|
if out_t then
|
|
local t_dval = type (dval)
|
|
if t_dval == out_t then
|
|
newsection[var] = dval
|
|
else
|
|
logreport ("both", 2, "conf",
|
|
"Section %d (%s): type mismatch of derived value of %q (%q, %s != %s); ignoring.",
|
|
i, title,
|
|
var, tostring (dval), t_dval, out_t)
|
|
end
|
|
else
|
|
newsection[var] = dval
|
|
end
|
|
else
|
|
logreport ("both", 2, "conf",
|
|
"Section %d (%s): value of %q could not be derived via %s from input %q; ignoring.",
|
|
i, title, var, t_transform, tostring (val))
|
|
end
|
|
else --- insert as is
|
|
newsection[var] = val
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return new
|
|
end
|
|
|
|
local function apply (old, new)
|
|
if not new then
|
|
if not old then
|
|
return false
|
|
end
|
|
return tablecopy (old)
|
|
elseif not old then
|
|
return tablecopy (new)
|
|
end
|
|
local result = tablecopy (old)
|
|
for name, section in next, new do
|
|
local t_section = type (section)
|
|
if t_section ~= table_t then
|
|
logreport ("both", 1, "conf",
|
|
"Error applying configuration: entry %s is %s, expected table.",
|
|
section, t_section)
|
|
--- ignore
|
|
else
|
|
local currentsection = result[name]
|
|
for var, val in next, section do
|
|
currentsection[var] = val
|
|
end
|
|
end
|
|
end
|
|
result.status = luaotfloadstatus
|
|
return result
|
|
end
|
|
|
|
local function reconfigure()
|
|
for i = 1, #reconf_tasks do
|
|
local name, task = unpack (reconf_tasks[i])
|
|
logreport ("both", 3, "conf", "Launch post-configuration task %q.", name)
|
|
if not task () then
|
|
logreport ("both", 0, "conf", "Post-configuration task %q failed.", name)
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function read (extra)
|
|
if extra then
|
|
add_config_paths (extra)
|
|
end
|
|
|
|
local readme = resolve_config_path ()
|
|
if readme == false then
|
|
logreport ("both", 2, "conf", "No configuration file.")
|
|
return false
|
|
end
|
|
|
|
local raw = ioloaddata (readme)
|
|
if not raw then
|
|
logreport ("both", 2, "conf", "Error reading the configuration file %q.", readme)
|
|
return false
|
|
end
|
|
|
|
local parsed = lpegmatch (config_parser, raw)
|
|
if not parsed then
|
|
logreport ("both", 2, "conf", "Error parsing configuration file %q.", readme)
|
|
return false
|
|
end
|
|
|
|
local ret, msg = process_options (parsed)
|
|
if not ret then
|
|
logreport ("both", 2, "conf", "File %q is not a valid configuration file.", readme)
|
|
logreport ("both", 2, "conf", "Error: %s", msg)
|
|
return false
|
|
end
|
|
return ret
|
|
end
|
|
|
|
local function apply_defaults ()
|
|
local defaults = default_config
|
|
local vars = read ()
|
|
--- Side-effects galore ...
|
|
config.luaotfload = apply (defaults, vars)
|
|
return reconfigure ()
|
|
end
|
|
|
|
local function dump ()
|
|
local sections = table.sortedkeys (config.luaotfload)
|
|
local confdata = { }
|
|
for i = 1, #sections do
|
|
local section = sections[i]
|
|
local vars = config.luaotfload[section]
|
|
local varnames = table.sortedkeys (vars)
|
|
local sformats = formatters[section]
|
|
if sformats then
|
|
confdata[#confdata + 1] = format_section (section)
|
|
for j = 1, #varnames do
|
|
local var = varnames[j]
|
|
local val = vars[var]
|
|
local comment, sformat = unpack (sformats[var] or { })
|
|
if not sformat then
|
|
comment, sformat = unpack (sformats.__default or { })
|
|
end
|
|
|
|
if sformat then
|
|
local dashedvar = dashed (var)
|
|
if comment then
|
|
confdata[#confdata + 1] = commented (sformat (dashedvar, val))
|
|
else
|
|
confdata[#confdata + 1] = sformat (dashedvar, val)
|
|
end
|
|
end
|
|
|
|
end
|
|
confdata[#confdata + 1] = ""
|
|
end
|
|
end
|
|
if next(confdata) then
|
|
iowrite (stringformat (conf_header,
|
|
osdate ("%Y-%m-d %H:%M:%S", os.time ())))
|
|
iowrite (tableconcat (confdata, "\n"))
|
|
iowrite (conf_footer)
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------
|
|
--- EXPORTS
|
|
-------------------------------------------------------------------------------
|
|
|
|
return function ()
|
|
config.luaotfload = { }
|
|
logreport = luaotfload.log.report
|
|
local parsers = luaotfload.parsers
|
|
config_parser = parsers.config
|
|
stripslashes = parsers.stripslashes
|
|
|
|
luaotfload.default_config = default_config
|
|
config.actions = {
|
|
read = read,
|
|
apply = apply,
|
|
apply_defaults = apply_defaults,
|
|
reconfigure = reconfigure,
|
|
dump = dump,
|
|
}
|
|
if not apply_defaults () then
|
|
logreport ("log", 0, "load",
|
|
"Configuration unsuccessful: error loading default settings.")
|
|
end
|
|
return true
|
|
end
|
|
|