diff options
Diffstat (limited to 'luaotfload-tool.lua')
-rwxr-xr-x | luaotfload-tool.lua | 437 |
1 files changed, 406 insertions, 31 deletions
diff --git a/luaotfload-tool.lua b/luaotfload-tool.lua index f1302a7..45a75ba 100755 --- a/luaotfload-tool.lua +++ b/luaotfload-tool.lua @@ -46,11 +46,12 @@ end 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 C, Ct, P = lpeg.C, lpeg.Ct, lpeg.P +local C, Ct, P, S = lpeg.C, lpeg.Ct, lpeg.P, lpeg.S local lpegmatch = lpeg.match local loader_file = "luatexbase.loader.lua" @@ -141,6 +142,8 @@ require"alt_getopt" local version = "2.3" -- same version number as luaotfload local names = fonts.names +local sanitize_string = names.sanitize_string + local db_src_out = names.path.dir.."/"..names.path.basename local db_bin_out = file.replacesuffix(db_src_out, "luc") @@ -181,7 +184,11 @@ This tool is part of the luaotfload package. Valid options are: -F --fuzzy look for approximate matches if --find fails --limit=n limit display of fuzzy matches to <n> (default: n = 1) - -i --info display font metadata + + -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> @@ -226,7 +233,10 @@ The font database will be saved to local help_msg = function ( ) local template = help_messages[config.luaotfload.self] or help_messages["luaotfload-tool"] - texiowrite_nl(stringformat(template, config.luaotfload.self, db_src_out, db_bin_out)) + texiowrite_nl(stringformat(template, + config.luaotfload.self, + db_src_out, + db_bin_out)) end local version_msg = function ( ) @@ -235,34 +245,386 @@ local version_msg = function ( ) config.luaotfload.self, version, names.version)) 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 show_info_items = function (fontinfo) - local items = table.sortedkeys(fontinfo) + local items = table.sortedkeys(fontinfo) + print_heading(fontinfo.fullname, 1) + texiowrite_nl "" for n = 1, #items do local item = items[n] texiowrite_nl(stringformat( - [[ %11s: %s]], item, fontinfo[item])) + info_fmt, item, fontinfo[item])) + end + 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", "l", "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 == "n" then + local v = fullinfo[key] + if v then + val = #fullinfo[key] + end + elseif mode == "d" then + val = os.date("%F %T", fullinfo[key]) + 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 + --inspect(languages.list) + 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 "" + print_heading("Features", 2) + print_heading("GSUB Features", 3) + display_feature_set(gsub) + print_heading("GPOS Features", 3) + display_feature_set(gpos) +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_string(font.fullname) == askedname then + return font + end + return subfont_by_name (lst, askedname, n+1) + end + return false end -local show_font_info = function (filename) - local fullname = resolvers.findfile(filename) +--[[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.filenames + local index = filenames.base[basename] + local fullname = filenames.full[index] + askedname = sanitize_string(askedname) + if not fullname then -- texmf + fullname = resolvers.findfile(basename) + end if fullname then - local fontinfo = fontloader.info(fullname) - local nfonts = #fontinfo + local shortinfo = fontloader.info(fullname) + local nfonts = #shortinfo if nfonts > 0 then -- true type collection - logs.names_report(true, 1, "resolve", - [[%s is a font collection]], filename) - for n = 1, nfonts do + local subfont + if askedname then + logs.names_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 logs.names_report(true, 1, "resolve", - [[showing info for font no. %d]], n) - show_info_items(fontinfo[n]) + [[%s is a font collection]], basename) + for subfont = 1, nfonts do + logs.names_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(fontinfo) + show_info_items(shortinfo) + if detail == true then + show_full_info(fullname, subfont, warnings) + end end else logs.names_report(true, 1, "resolve", - "font %s not found", filename) + "Font %s not found", filename) end end @@ -286,8 +648,8 @@ local actions = { } --- (jobspec -> (bool * bool)) list actions.loglevel = function (job) logs.set_loglevel(job.log_level) logs.names_report("info", 3, "util", - "setting log level", "%d", job.log_level) - logs.names_report("log", 0, "util", "lua=%s", _VERSION) + "Setting log level", "%d", job.log_level) + logs.names_report("log", 0, "util", "Lua=%s", _VERSION) return true, true end @@ -374,11 +736,11 @@ actions.query = function (job) "Resolved file name “%s”, subfont nr. “%s”", foundname, subfont) else - logs.names_report(false, 0, - "resolve", "Resolved file name “%s”", foundname) + logs.names_report(false, 0, "resolve", + "Resolved file name “%s”", foundname) end if job.show_info then - show_font_info(foundname) + show_font_info(foundname, query, job.full_info, job.warnings) end else logs.names_report(false, 0, @@ -469,7 +831,7 @@ actions.list = function (job) local nmappings = #mappings if criterion == "*" then - logs.names_report(false, 1, "list", "all %d entries", nmappings) + logs.names_report(false, 1, "list", "All %d entries", nmappings) for i=1, nmappings do local entry = mappings[i] local fields = get_fields(entry, asked_fields) @@ -484,12 +846,12 @@ actions.list = function (job) criterion = criterion[1] asked_fields = set_primary_field(asked_fields, criterion) - logs.names_report(false, 1, "list", "by %s", criterion) + logs.names_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 - logs.names_report(false, 2, "list", "restricting to value %s", asked_value) + logs.names_report(false, 2, "list", "Restricting to value %s", asked_value) for i=1, nmappings do local entry = mappings[i] if entry[criterion] @@ -553,6 +915,8 @@ 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 @@ -569,6 +933,7 @@ local process_cmdline = function ( ) -- unit -> jobspec fuzzy = "F", help = "h", info = "i", + inspect = "I", limit = 1, list = 1, log = 1, @@ -578,9 +943,10 @@ local process_cmdline = function ( ) -- unit -> jobspec update = "u", verbose = 1 , version = "V", + warnings = "w", } - local short_options = "bDfFilpquvVh" + local short_options = "bDfFiIlpquvVhw" local options, _, optarg = alt_getopt.get_ordered_opts (arg, short_options, long_options) @@ -608,8 +974,14 @@ local process_cmdline = function ( ) -- unit -> jobspec elseif v == "verbose" then local lvl = optarg[n] if lvl then - result.log_level = tonumber(lvl) + 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 @@ -627,6 +999,9 @@ local process_cmdline = function ( ) -- unit -> jobspec end elseif v == "i" then result.show_info = true + elseif v == "I" then + result.show_info = true + result.full_info = true elseif v == "alias" then config.luaotfload.self = optarg[n] elseif v == "l" then @@ -667,7 +1042,7 @@ local main = function ( ) -- unit -> int local actionname = action_sequence[i] local exit = false if action_pending[actionname] then - logs.names_report("log", 3, "util", "preparing for task", + logs.names_report("log", 3, "util", "Preparing for task", "%s", actionname) local action = actions[actionname] @@ -675,16 +1050,16 @@ local main = function ( ) -- unit -> int if not success then logs.names_report(false, 0, "util", - "could not finish task", "%s", actionname) + "Could not finish task", "%s", actionname) retval = -1 exit = true elseif not continue then logs.names_report(false, 3, "util", - "task completed, exiting", "%s", actionname) + "Task completed, exiting", "%s", actionname) exit = true else logs.names_report(false, 3, "util", - "task completed successfully", "%s", actionname) + "Task completed successfully", "%s", actionname) end end if exit then break end |