From 69f51727fba1c348d78348d9f163cf884e7ab558 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg42.2a@gmail.com>
Date: Thu, 9 May 2013 13:45:14 +0200
Subject: add querying of font metadata to luaotfload-tool

---
 luaotfload-auxiliary.lua |   4 ++
 luaotfload-database.lua  |  26 +++++----
 luaotfload-tool.lua      | 147 +++++++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 162 insertions(+), 15 deletions(-)

diff --git a/luaotfload-auxiliary.lua b/luaotfload-auxiliary.lua
index 2d459d0..9f7974a 100644
--- a/luaotfload-auxiliary.lua
+++ b/luaotfload-auxiliary.lua
@@ -64,6 +64,10 @@ local add_fontdata_fallbacks = function (fontdata)
       metadata = fontdata.shared.rawdata.metadata
       fontdata.units   = fontparameters.units
       local resources  = fontdata.resources
+      --- the next line is a hack that fixes scaling of fonts with
+      --- non-standard em-sizes (most ms fonts have 2048, others
+      --- come with 256)
+      --- this is considered a bug in the font loader
       fontdata.size    = fontparameters.size * fontdata.units / 1000
       --- for legacy fontspec.lua and unicode-math.lua
       fontdata.shared.otfdata          = metadata
diff --git a/luaotfload-database.lua b/luaotfload-database.lua
index 576341f..96db195 100644
--- a/luaotfload-database.lua
+++ b/luaotfload-database.lua
@@ -146,10 +146,11 @@ This is a sketch of the luaotfload db:
             psname     : string;
             subfamily  : string;
         }
-        size        : int list;
-        slant       : int;
-        weight      : int;
-        width       : int;
+        size         : int list;
+        slant        : int;
+        weight       : int;
+        width        : int;
+        units_per_em : int;                      // mainly 1000, but also 2048 or 256
     }
     and filestatus = (fullname, { index : int list; timestamp : int }) dict
 
@@ -836,13 +837,16 @@ font_fullinfo = function (filename, subfont)
         report("log", 1, "db", "broken font rejected", "%s", basefile)
         return
     end
-    tfmdata.fontname    = metadata.fontname
-    tfmdata.fullname    = metadata.fullname
-    tfmdata.familyname  = metadata.familyname
-    tfmdata.filename    = { filename, subfont } -- always store full path
-    tfmdata.weight      = metadata.pfminfo.weight
-    tfmdata.width       = metadata.pfminfo.width
-    tfmdata.slant       = metadata.italicangle
+    tfmdata.fontname      = metadata.fontname
+    tfmdata.fullname      = metadata.fullname
+    tfmdata.familyname    = metadata.familyname
+    tfmdata.filename      = { filename, subfont } -- always store full path
+    tfmdata.weight        = metadata.pfminfo.weight
+    tfmdata.width         = metadata.pfminfo.width
+    tfmdata.slant         = metadata.italicangle
+    --- this is for querying
+    tfmdata.units_per_em  = metadata.units_per_em
+    tfmdata.version       = metadata.version
     -- don't waste the space with zero values
     tfmdata.size = {
         metadata.design_size         ~= 0 and metadata.design_size         or nil,
diff --git a/luaotfload-tool.lua b/luaotfload-tool.lua
index 0cd19b1..6662291 100755
--- a/luaotfload-tool.lua
+++ b/luaotfload-tool.lua
@@ -32,6 +32,8 @@ local stringformat    = string.format
 local texiowrite_nl   = texio.write_nl
 local stringlower     = string.lower
 
+local C, Ct, P     = lpeg.C, lpeg.Ct, lpeg.P
+local lpegmatch    = lpeg.match
 
 local loader_file = "luatexbase.loader.lua"
 local loader_path = assert(kpse.find_file(loader_file, "lua"),
@@ -64,8 +66,6 @@ local config        = config
 config.luaotfload   = config.luaotfload or { }
 
 do -- we don’t have file.basename and the likes yet, so inline parser ftw
-    local C, P         = lpeg.C, lpeg.P
-    local lpegmatch    = lpeg.match
     local slash        = P"/"
     local dot          = P"."
     local noslash      = 1 - slash
@@ -228,7 +228,7 @@ set.
 --]]--
 
 local action_sequence = {
-    "loglevel", "help", "version", "flush", "generate", "query"
+    "loglevel", "help", "version", "flush", "generate", "list", "query"
 }
 local action_pending  = table.tohash(action_sequence, false)
 
@@ -286,7 +286,7 @@ actions.query = function (job)
         lookup        = "name",
         specification = "name:" .. query,
         optsize       = 0,
-    }
+   }
 
     local foundname, subfont, success =
         fonts.names.resolve(nil, nil, tmpspec)
@@ -317,6 +317,137 @@ actions.query = function (job)
     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 value = entry[field]
+        acc[#acc+1] = value or false
+        return get_fields(entry, fields, acc, n+1)
+    end
+    return acc
+end
+
+local comma       = P","
+local noncomma    = 1-comma
+local split_comma = Ct((C(noncomma^1) + comma)^1)
+
+local texiowrite_nl     = texio.write_nl
+local tableconcat       = table.concat
+local stringexplode     = string.explode
+
+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
+
+actions.list = function (job)
+    local criterion     = job.criterion
+
+    local asked_fields  = job.asked_fields
+    if asked_fields then
+        asked_fields = lpegmatch(split_comma, asked_fields)
+    else
+        --- some defaults
+        asked_fields = { "fullname", "version", }
+    end
+
+    if not names.data then
+        names.data = names.load()
+    end
+
+    local mappings  = names.data.mappings
+    local nmappings = #mappings
+
+    if criterion == "*" then
+        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)
+            --- 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)
+
+        logs.names_report(false, 1, "list", "by %s", criterion)
+
+        --- firstly, build a list of fonts to operate on
+        local targets, ntargets
+
+        if asked_value then
+            logs.names_report(false, 2, "list", "restricting to value %s", asked_value)
+            targets = { }
+            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
+            ntargets = #targets
+        else --- all
+            targets  = mappings
+            ntargets = nmappings
+        end
+        logs.names_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
+
+    return true, true
+end
+
 --[[--
 Command-line processing.
 mkluatexfontdb.lua relies on the script alt_getopt to process argv and
@@ -330,6 +461,7 @@ alt_getopt.
 local process_cmdline = function ( ) -- unit -> jobspec
     local result = { -- jobspec
         force_reload = nil,
+        criterion    = "",
         query        = "",
         log_level    = 1, --- 2 is approx. the old behavior
     }
@@ -337,12 +469,14 @@ local process_cmdline = function ( ) -- unit -> jobspec
     local long_options = {
         alias            = 1,
         ["flush-cache"]  = "c",
+        fields           = 1,
         find             = 1,
         force            = "f",
         fuzzy            = "F",
         help             = "h",
         info             = "i",
         limit            = 1,
+        list             = 1,
         log              = 1,
         quiet            = "q",
         update           = "u",
@@ -401,6 +535,11 @@ local process_cmdline = function ( ) -- unit -> jobspec
             config.luaotfload.self = optarg[n]
         elseif v == "c" 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]
         end
     end
 
-- 
cgit v1.2.3