diff options
| -rw-r--r-- | NEWS | 6 | ||||
| -rw-r--r-- | filegraph.dot | 3 | ||||
| -rw-r--r-- | luaotfload-basics-gen.lua | 4 | ||||
| -rw-r--r-- | luaotfload-database.lua | 493 | ||||
| -rw-r--r-- | luaotfload-features.lua | 2 | ||||
| -rw-r--r-- | luaotfload-lib-dir.lua | 470 | ||||
| -rw-r--r-- | luaotfload-merged.lua | 6 | ||||
| -rw-r--r-- | luaotfload-override.lua | 82 | ||||
| -rwxr-xr-x | luaotfload-tool.lua | 496 | ||||
| -rw-r--r-- | luaotfload-tool.rst | 24 | ||||
| -rw-r--r-- | luaotfload.dtx | 59 | 
11 files changed, 924 insertions, 721 deletions
| @@ -1,7 +1,7 @@  Change History  -------------- -2013/xx/xx, luaotfload v2.3: +2013/07/03, luaotfload v2.3:      * New experimental lookups: ``kpse`` (kpathsea), ``my`` (callback)      * Precedence of texmf over system fonts can be requested        using the ``--prefer-texmf`` option of luaotfload-tool @@ -9,6 +9,10 @@ Change History      * Rewrite the output of ``\fontname`` via ``patch_font`` callback      * Allow blacklisting directories      * Drop compatibility layer +    * Remove dependency on luaotfload-lib-dir (references to dir.glob() and +      dir.mkdirs()) +    * luaotfload-tool now displays extensive information about a font if given +      the argument --inspect  2013/05/20, luaotfload v2.2:      * There is now a central, non-personal dev repo on github: diff --git a/filegraph.dot b/filegraph.dot index 8db137c..c8a07d3 100644 --- a/filegraph.dot +++ b/filegraph.dot @@ -139,10 +139,9 @@ strict digraph luaotfload_files { //looks weird with circo ...          label      = <              <table cellborder="0" bgcolor="#FFFFFFAA">                  <th> <td colspan="2"> <font point-size="12" face="Iwona Italic">Luaotfload Libraries</font> </td> </th> -                <tr> <td>luaotfload-lib-dir.lua</td>   <td>luaotfload-features.lua</td> </tr> +                <tr> <td>luaotfload-auxiliary.lua</td> <td>luaotfload-features.lua</td> </tr>                  <tr> <td>luaotfload-override.lua</td>  <td>luaotfload-loaders.lua</td>  </tr>                  <tr> <td>luaotfload-database.lua</td>  <td>luaotfload-color.lua</td>    </tr> -                <tr> <td>luaotfload-auxiliary.lua</td>                                  </tr>              </table>          >,      ] diff --git a/luaotfload-basics-gen.lua b/luaotfload-basics-gen.lua index 4a46fbb..a5da2fd 100644 --- a/luaotfload-basics-gen.lua +++ b/luaotfload-basics-gen.lua @@ -151,13 +151,13 @@ do      -- quite like tex live or so -    if cachepaths == "" then +    if cachepaths == "$TEXMFCACHE" then          cachepaths = kpse.expand_var('$TEXMFVAR') or ""      end      -- this also happened to be used -    if cachepaths == "" then +    if cachepaths == "$TEXMFVAR" then          cachepaths = kpse.expand_var('$VARTEXMF') or ""      end diff --git a/luaotfload-database.lua b/luaotfload-database.lua index 484decf..c0aadaf 100644 --- a/luaotfload-database.lua +++ b/luaotfload-database.lua @@ -16,8 +16,8 @@ local lpeg = require "lpeg"  local P, R, S, lpegmatch      = lpeg.P, lpeg.R, lpeg.S, lpeg.match -local C, Cc, Cf, Cg, Ct -    = lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Ct +local C, Cc, Cf, Cg, Cs, Ct +    = lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Cs, lpeg.Ct  --- Luatex builtins  local load                    = load @@ -33,12 +33,11 @@ local iolines                 = io.lines  local ioopen                  = io.open  local kpseexpand_path         = kpse.expand_path  local kpseexpand_var          = kpse.expand_var -local kpselookup              = kpse.lookup  local kpsefind_file           = kpse.find_file +local kpselookup              = kpse.lookup  local kpsereadable_file       = kpse.readable_file -local lfsisdir                = lfs.isdir -local lfsisfile               = lfs.isfile  local lfsattributes           = lfs.attributes +local lfsdir                  = lfs.dir  local mathabs                 = math.abs  local mathmin                 = math.min  local stringfind              = string.find @@ -49,29 +48,30 @@ local stringlower             = string.lower  local stringsub               = string.sub  local stringupper             = string.upper  local tableconcat             = table.concat -local tablecopy               = table.copy  local tablesort               = table.sort -local tabletofile             = table.tofile  local texiowrite_nl           = texio.write_nl  local utf8gsub                = unicode.utf8.gsub  local utf8lower               = unicode.utf8.lower  --- these come from Lualibs/Context -local dirglob                 = dir.glob -local dirmkdirs               = dir.mkdirs  local filebasename            = file.basename -local filenameonly            = file.nameonly -local filedirname             = file.dirname  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 lfsisdir                = lfs.isdir +local lfsisfile               = lfs.isfile +local lfsmkdirs               = lfs.mkdirs  local stringis_empty          = string.is_empty  local stringsplit             = string.split  local stringstrip             = string.strip  local tableappend             = table.append +local tablecopy               = table.copy +local tabletofile             = table.tofile  local tabletohash             = table.tohash  --- the font loader namespace is “fonts”, same as in Context @@ -83,32 +83,33 @@ fonts.definers       = fonts.definers or { }  local names          = fonts.names +config                         = config or { } +config.luaotfload              = config.luaotfload or { } +config.luaotfload.resolver     = config.luaotfload.resolver or "normal" +if config.luaotfload.update_live ~= false then +    --- this option allows for disabling updates +    --- during a TeX run +    config.luaotfload.update_live = true +end +  names.version        = 2.207  names.data           = nil      --- contains the loaded database  names.lookups        = nil      --- contains the lookup cache  names.path           = {      dir              = "",                      --- db and cache directory -    basename         = "luaotfload-names.lua",  --- db file name +    basename         = config.luaotfload.names_file +                    or "luaotfload-names.lua",      path             = "",                      --- full path to db file      lookup_basename  = "luaotfload-lookup-cache.lua", --- cache file name      lookup_path      = "",                            --- cache full path  } -config                         = config or { } -config.luaotfload              = config.luaotfload or { } -config.luaotfload.resolver     = config.luaotfload.resolver or "normal" -if config.luaotfload.update_live ~= false then -    --- this option allows for disabling updates -    --- during a TeX run -    config.luaotfload.update_live = true -end -  -- We use the cache.* of ConTeXt (see luat-basics-gen), we can  -- use it safely (all checks and directory creations are already done). It  -- uses TEXMFCACHE or TEXMFVAR as starting points.  local writable_path  if caches then -    writable_path = caches.getwritablepath("names","") +    writable_path = caches.getwritablepath "names"      if not writable_path then          luaotfload.error("Impossible to find a suitable writeable cache...")      end @@ -136,6 +137,47 @@ local sanitize_string = function (str)      return nil  end +local find_files_indeed +find_files_indeed = function (acc, dirs, filter) +    if not next (dirs) then --- done +        return acc +    end + +    local dir   = dirs[#dirs] +    dirs[#dirs] = nil + +    local newdirs, 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 +                    newdirs[#newdirs+1] = fullpath +                elseif lfsisfile (fullpath) then +                    newfiles[#newfiles+1] = fullpath +                end +            end +        end +    end +    return find_files_indeed (tableappend (acc, newfiles), +                              tableappend (dirs, newdirs), +                              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: @@ -295,6 +337,17 @@ load_names = function (dry_run)              "Font names database loaded", "%s", foundname)          report("info", 3, "db", "Loading took %0.f ms",                                  1000*(os.gettimeofday()-starttime)) + +        local db_version, nms_version = data.version, names.version +        if db_version ~= nms_version then +            report("log", 0, "db", +                [[Version mismatch; expected %4.3f, got %4.3f]], +                nms_version, db_version) +            if not fonts_reloaded then +                report("log", 0, "db", [[force rebuild]]) +                return update_names({ }, true, false) +            end +        end      else          report("both", 0, "db",              [[Font names database not found, generating new one.]]) @@ -326,22 +379,51 @@ end  local style_synonyms = { set = { } }  do +    local combine = function (ta, tb) +        local result = { } +        for i=1, #ta do +            for j=1, #tb do +                result[#result+1] = ta[i] .. tb[j] +            end +        end +        return result +    end +      --- read this: http://blogs.adobe.com/typblography/2008/05/indesign_font_conflicts.html      --- tl;dr: font style synonyms are unreliable. -    style_synonyms.list = { +    --- +    --- Context matches font names against lists of known identifiers +    --- for weight, style, width, and variant, so that including +    --- the family name there are five dimensions for choosing a +    --- match. The sad thing is, while this is a decent heuristic it +    --- makes no sense to imitate it in luaotfload because the user +    --- interface must fit into the much more limited Xetex scheme that +    --- distinguishes between merely four style categories (variants): +    --- “regular”, “italic”, “bold”, and “bolditalic”. As a result, +    --- some of the styles are lumped together although they can differ +    --- significantly (like “medium” and “bold”). + +    --- Xetex (XeTeXFontMgr.cpp) appears to recognize only “plain”, +    --- “normal”, and “roman” as synonyms for “regular”. +    local list = {          regular    = { "normal",         "roman", -                       "plain",          "book", }, +                       "plain",          "book", +                       "light",          "extralight", +                       "ultralight", },          bold       = { "demi",           "demibold",                         "semibold",       "boldregular", -                       "medium" }, +                       "medium",         "mediumbold", +                       "ultrabold",      "extrabold", +                       "heavy",          "black", +                       "bold", },          italic     = { "regularitalic",  "normalitalic", -                       "oblique",        "slanted", }, -        bolditalic = { "boldoblique",    "boldslanted", -                       "demiitalic",     "demioblique", -                       "demislanted",    "demibolditalic", -                       "semibolditalic", }, +                       "oblique",        "slanted", +                       "italic", },      } +    list.bolditalic     = combine(list.bold, list.italic) +    style_synonyms.list = list +      for category, synonyms in next, style_synonyms.list do          style_synonyms.set[category] = tabletohash(synonyms, true)      end @@ -358,12 +440,12 @@ local verbose_lookup = function (data, kind, filename)          found = data.full[found]          if found == nil then --> texmf              report("info", 0, "db", -                "crude file lookup: req=%s; hit=%s => kpse", +                "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", +                "Crude file lookup: req=%s; hit=%s; ret=%s",                  filename, kind, found)          end          return found @@ -538,23 +620,23 @@ end  resolve_cached = function (_, _, specification)      if not names.lookups then names.lookups = load_lookups() end      local request = hash_request(specification) -    report("both", 4, "cache", "looking for “%s” in cache ...", +    report("both", 4, "cache", "Looking for “%s” in cache ...",             request)      local found = names.lookups[request]      --- case 1) cache positive ----------------------------------------      if found then --- replay fields from cache hit -        report("info", 4, "cache", "found!") +        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") +        report("both", 4, "cache", "Cached file not found; resolving again")      else -        report("both", 4, "cache", "not cached; resolving") +        report("both", 4, "cache", "Not cached; resolving")      end      --- case 2) cache negative ---------------------------------------- @@ -563,16 +645,16 @@ resolve_cached = function (_, _, specification)      if not success then return filename, subfont, false end      --- ... then we add the fields to the cache ... ...      local entry = { filename, subfont } -    report("both", 4, "cache", "new entry: %s", request) +    report("both", 4, "cache", "New entry: %s", request)      names.lookups[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") +    report("both", 5, "cache", "Saving updated cache")      local success = save_lookups()      if not success then --- sad, but not critical -        report("both", 0, "cache", "could not write to cache") +        report("both", 0, "cache", "Could not write to cache")      end      return filename, subfont, true  end @@ -580,9 +662,16 @@ 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,   optsize, dsnsize, size, -    minsize, maxsize, face) +local add_to_match = function (found, size, face) + +    local optsize, dsnsize, maxsize, minsize +    if #face.size > 0 then +        optsize = face.size +        dsnsize = optsize[1] and optsize[1] / 10 +        -- can be nil +        maxsize = optsize[2] and optsize[2] / 10 or dsnsize +        minsize = optsize[3] and optsize[3] / 10 or dsnsize +    end      local continue = true      if optsize then          if dsnsize == size or (size > minsize and size <= maxsize) then @@ -637,18 +726,18 @@ the font database created by the luaotfload-tool script.  ---     values.  --- -resolve = function (_,_,specification) -- the 1st two parameters are used by ConTeXt +resolve = function (_, _, specification) -- the 1st two parameters are used by ConTeXt      if not fonts_loaded then names.data = load_names() end      local data = names.data      local name  = sanitize_string(specification.name)      local style = sanitize_string(specification.style) or "regular" -    local size +    local askedsize      if specification.optsize then -        size = tonumber(specification.optsize) +        askedsize = tonumber(specification.optsize)      elseif specification.size then -        size = specification.size / 65536 +        askedsize = specification.size / 65536      end      if type(data) ~= "table" then @@ -665,18 +754,6 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con          return specification.name, false, false      end -    local db_version, nms_version = data.version, names.version -    if db_version ~= nms_version then -        report("log", 0, "db", -            [[version mismatch; expected %4.3f, got %4.3f]], -            nms_version, db_version) -        if not fonts_reloaded then -            return reload_db("version mismatch", -                             resolve, nil, nil, specification) -        end -        return specification.name, false, false -    end -      if not data.mappings then          if not fonts_reloaded then              return reload_db("invalid database; missing font mapping", @@ -685,7 +762,8 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con          return specification.name, false, false      end -    local found      = { } --> collect results +    local exact      = { } --> collect exact style matches +    local synonymous = { } --> collect matching style synonyms      local fallback         --> e.g. non-matching style (fontspec is anal about this)      local candidates = { } --> secondary results, incomplete matches @@ -708,47 +786,31 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con          fontname    = fontname  or sanitize_string(face.fontname)          pfullname   = pfullname or sanitize_string(face.fullname) -        local optsize, dsnsize, maxsize, minsize -        if #face.size > 0 then -            optsize = face.size -            dsnsize = optsize[1] and optsize[1] / 10 -            -- can be nil -            maxsize = optsize[2] and optsize[2] / 10 or dsnsize -            minsize = optsize[3] and optsize[3] / 10 or dsnsize -        end -          if     name == family              or name == metafamily          then -            if     style == prefmodifiers -                or style == subfamily -                or synonym_set[style] and -                    (synonym_set[style][prefmodifiers] or -                     synonym_set[style][subfamily]) -            then +            if style == prefmodifiers then                  local continue -                found, continue = add_to_match( -                    found,   optsize, dsnsize, size, -                    minsize, maxsize, face) +                exact, continue = add_to_match(exact, askedsize, face)                  if continue == false then break end - -            elseif prefmodifiers == "regular" -                or subfamily     == "regular" -                --- TODO this match should be performed when building the db -                or synonym_set.regular[prefmodifiers] -                or synonym_set.regular[subfamily] -            then -                fallback = face +            elseif style == subfamily then +                exact = add_to_match(exact, askedsize, face)              elseif name == fullname                  or name == pfullname                  or name == fontname                  or name == psname              then -                local continue -                found, continue = add_to_match( -                    found,   optsize, dsnsize, size, -                    minsize, maxsize, face) -                if continue == false then break end +                synonymous, continue = add_to_match(synonymous, askedsize, face) +            elseif synonym_set[style] and +                    (synonym_set[style][prefmodifiers] or +                     synonym_set[style][subfamily]) +                or synonym_set.regular[prefmodifiers] +                or synonym_set.regular[subfamily] +            then +                synonymous = add_to_match(synonymous, askedsize, face) +            elseif prefmodifiers == "regular" +                or subfamily     == "regular" then +                fallback = face              else --- mark as last straw but continue                  candidates[#candidates+1] = face              end @@ -758,14 +820,19 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con              or name == fontname              or name == psname then                  local continue -                found, continue = add_to_match( -                    found,   optsize, dsnsize, size, -                    minsize, maxsize, face) +                exact, continue = add_to_match(exact, askedsize, face)                  if continue == false then break end              end          end      end +    local found +    if next(exact) then +        found = exact +    else +        found = synonymous +    end +      --- this is a monster      if #found == 1 then          --- “found” is really synonymous with “registered in the db”. @@ -774,7 +841,7 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con              = get_font_file(data.filenames.full, entry)          if success == true then              report("log", 0, "resolve", -                "font family='%s', subfamily='%s' found: %s", +                "Font family='%s', subfamily='%s' found: %s",                  name, style, filename              )              return filename, subfont, true @@ -787,7 +854,7 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con          local least = math.huge -- initial value is infinity          for i,face in next, found do              local dsnsize    = face.size[1]/10 -            local difference = mathabs(dsnsize-size) +            local difference = mathabs(dsnsize - askedsize)              if difference < least then                  closest = face                  least   = difference @@ -797,7 +864,7 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con              = get_font_file(data.filenames.full, closest)          if success == true then              report("log", 0, "resolve", -                "font family='%s', subfamily='%s' found: %s", +                "Font family='%s', subfamily='%s' found: %s",                  name, style, filename              )              return filename, subfont, true @@ -807,11 +874,11 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con              = get_font_file(data.filenames.full, fallback)          if success == true then              report("log", 0, "resolve", -                "no exact match for request %s; using fallback", +                "No exact match for request %s; using fallback",                  specification.specification              )              report("log", 0, "resolve", -                "font family='%s', subfamily='%s' found: %s", +                "Font family='%s', subfamily='%s' found: %s",                  name, style, filename              )              return filename, subfont, true @@ -823,7 +890,7 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con              = get_font_file(data.filenames.full, entry)          if success == true then              report("log", 0, "resolve", -                "font family='%s', subfamily='%s' found: %s", +                "Font family='%s', subfamily='%s' found: %s",                  name, style, filename              )              return filename, subfont, true @@ -861,7 +928,7 @@ end  --- string -> ('a -> 'a) -> 'a list -> 'a  reload_db = function (why, caller, ...) -    report("both", 1, "db", "reload initiated; reason: “%s”", why) +    report("both", 1, "db", "Reload initiated; reason: “%s”", why)      names.data = update_names(names.data, false, false)      local success = save_names()      if success then @@ -957,13 +1024,13 @@ find_closest = function (name, limit)          tablesort(distances)          limit = mathmin(n_distances, limit)          report(false, 1, "query", -                "displaying %d distance levels", limit) +               "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 “" .. name .. "”: " .. dist +                   "Distance from “" .. name .. "”: " .. dist                  .. "\n    " .. tableconcat(namelst, "\n    ")              )          end @@ -991,7 +1058,7 @@ font_fullinfo = function (filename, subfont, texmf, basename)      local tfmdata = { }      local rawfont = fontloaderopen(filename, subfont)      if not rawfont then -        report("log", 1, "error", "failed to open %s", filename) +        report("log", 1, "error", "Failed to open %s", filename)          return      end      local metadata = fontloader.to_table(rawfont) @@ -1029,7 +1096,7 @@ font_fullinfo = function (filename, subfont, texmf, basename)          end      else          -- no names table, propably a broken font -        report("log", 1, "db", "broken font rejected", "%s", basefile) +        report("log", 1, "db", "Broken font rejected", "%s", basefile)          return      end      tfmdata.fontname      = metadata.fontname @@ -1081,7 +1148,7 @@ local load_font = function (fullname, fontnames, newfontnames, texmf)      if names.blacklist[fullname] or names.blacklist[basename]      then          report("log", 2, "db", -            "ignoring blacklisted font “%s”", fullname) +               "Ignoring blacklisted font “%s”", fullname)          return false      end @@ -1112,7 +1179,7 @@ local load_font = function (fullname, fontnames, newfontnames, texmf)              newmappings[location]          = fullinfo --- keep              newentrystatus.index[index+1]  = location --- is this actually used anywhere?          end -        report("log", 2, "db", "font “%s” already indexed", basename) +        report("log", 2, "db", "Font “%s” already indexed", basename)          return false      end @@ -1145,7 +1212,7 @@ local load_font = function (fullname, fontnames, newfontnames, texmf)          end      else --- missing info -        report("log", 1, "db", "failed to load “%s”", basename) +        report("log", 1, "db", "Failed to load “%s”", basename)          return false      end      return true @@ -1170,8 +1237,8 @@ do              return path          end  --[[doc-- -    Cygwin used to be treated different from windows and dos.  This -    special treatment was removed with a patch submitted by Ken Brown. +    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]]-- @@ -1226,7 +1293,7 @@ local create_blacklist = function (blacklist, whitelist)      local result = { }      local dirs   = { } -    report("info", 2, "db", "blacklisting “%d” files and directories", +    report("info", 2, "db", "Blacklisting “%d” files and directories",             #blacklist)      for i=1, #blacklist do          local entry = blacklist[i] @@ -1237,7 +1304,7 @@ local create_blacklist = function (blacklist, whitelist)          end      end -    report("info", 2, "db", "whitelisting “%d” files", #whitelist) +    report("info", 2, "db", "Whitelisting “%d” files", #whitelist)      for i=1, #whitelist do          result[whitelist[i]] = nil      end @@ -1265,12 +1332,12 @@ end  --- unit -> unit  read_blacklist = function ()      local files = { -        kpselookup("luaotfload-blacklist.cnf", {all=true, format="tex"}) +        kpselookup ("luaotfload-blacklist.cnf", +                    {all=true, format="tex"})      }      local blacklist = { }      local whitelist = { } -    --- TODO lpegify      if files and type(files) == "table" then          for _,v in next, files do              for line in iolines(v) do @@ -1286,7 +1353,7 @@ read_blacklist = function ()                          line = stringsub(line, 1, cmt - 1)                      end                      line = stringstrip(line) -                    report("log", 2, "db", "blacklisted file “%s”", line) +                    report("log", 2, "db", "Blacklisted file “%s”", line)                      blacklist[#blacklist+1] = line                  end              end @@ -1295,10 +1362,59 @@ read_blacklist = function ()      names.blacklist = create_blacklist(blacklist, whitelist)  end -local font_extensions = { "otf", "ttf", "ttc", "dfont" } -local font_extensions_set = {} -for key, value in next, font_extensions do -   font_extensions_set[value] = true +local font_extensions     = { "otf", "ttf", "ttc", "dfont" } +local font_extensions_set = tabletohash (font_extensions) +local p_font_extensions +do +    local extns +    --tablesort (font_extensions) --- safeguard +    for i=#font_extensions, 1, -1 do +        local e = font_extensions[i] +        if not extns then +            extns = P(e) +        else +            extns = extns + P(e) +        end +    end +    extns = extns * P(-1) +    p_font_extensions = (1 - extns)^1 * extns +end + +local process_dir_tree +process_dir_tree = function (acc, dirs) +    if not next (dirs) then --- done +        return acc +    end + +    local dir   = dirs[#dirs] +    dirs[#dirs] = nil + +    local newdirs, 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 +                newdirs[#newdirs+1] = fullpath +            elseif lfsisfile (fullpath) then +                if lpegmatch (p_font_extensions, stringlower(ent)) then +                    newfiles[#newfiles+1] = fullpath +                end +            end +        end +    end +    return process_dir_tree (tableappend (acc, newfiles), +                             tableappend (dirs, newdirs)) +end + +--- string -> string list +local find_font_files = function (root) +    if lfsisdir (root) then +        return process_dir_tree ({}, { root }) +    end  end  --[[doc-- @@ -1314,40 +1430,45 @@ end  --doc]]--  --- string -> dbobj -> dbobj -> bool -> bool -> (int * int) -local scan_dir = function (dirname, fontnames, newfontnames, dry_run, texmf) -    if lpegmatch(p_blacklist, dirname) then +local scan_dir = function (dirname, fontnames, newfontnames, +                           dry_run, texmf) +    if lpegmatch (p_blacklist, dirname) then +        report ("both", 3, "db", +                "Skipping blacklisted directory %s", dirname)          --- ignore          return 0, 0      end - -    local n_scanned, n_new = 0, 0   --- total of fonts collected -    report("both", 3, "db", "scanning directory %s", dirname) -    for _,i in next, font_extensions do -        for _,ext in next, { i, stringupper(i) } do -            local found = dirglob(stringformat("%s/**.%s$", dirname, ext)) -            local n_found = #found -            --- note that glob fails silently on broken symlinks, which -            --- happens sometimes in TeX Live. -            report("both", 4, "db", "%s '%s' fonts found", n_found, ext) -            n_scanned = n_scanned + n_found -            for j=1, n_found do -                local fullname = found[j] -                fullname = path_normalize(fullname) -                local new -                if dry_run == true then -                    report("both", 1, "db", "would have been loading “%s”", fullname) -                else -                    report("both", 4, "db", "loading font “%s”", fullname) -                    local new = load_font(fullname, fontnames, newfontnames, texmf) -                    if new == true then -                        n_new = n_new + 1 -                    end -                end +    local found = find_font_files (dirname) +    if not found then +        report ("both", 3, "db", +                "No such directory: “%s”; skipping.", dirname) +        return 0, 0 +    end +    report ("both", 3, "db", "Scanning directory %s", dirname) + +    local n_new = 0   --- total of fonts collected +    local n_found = #found +    report ("both", 4, "db", "%d font files detected", n_found) +    for j=1, n_found do +        local fullname = found[j] +        fullname = path_normalize(fullname) +        local new +        if dry_run == true then +            report ("both", 1, "db", +                    "Would have been loading “%s”", fullname) +        else +            report ("both", 4, "db", +                    "Loading font “%s”", fullname) +            local new = load_font (fullname, fontnames, +                                   newfontnames, texmf) +            if new == true then +                n_new = n_new + 1              end          end      end -    report("both", 4, "db", "%d fonts found in '%s'", n_scanned, dirname) -    return n_scanned, n_new + +    report("both", 4, "db", "%d fonts found in '%s'", n_found, dirname) +    return n_found, n_new  end  --- dbobj -> dbobj -> bool? -> (int * int) @@ -1366,7 +1487,6 @@ local scan_texmf_fonts = function (fontnames, newfontnames, dry_run)      fontdirs       = fontdirs .. stringgsub(kpseexpand_path("$TTFONTS"), "^%.", "")      if not stringis_empty(fontdirs) then          for _,d in next, filesplitpath(fontdirs) do -            report("info", 4, "db", "Entering directory %s", d)              local found, new = scan_dir(d, fontnames, newfontnames, dry_run, true)              n_scanned = n_scanned + found              n_new     = n_new     + new @@ -1471,7 +1591,7 @@ do --- closure for read_fonts_conf()      local fonts_conf_scanner = function (path)          local fh = ioopen(path, "r")          if not fh then -            report("both", 3, "db", "cannot open fontconfig file %s", path) +            report("both", 3, "db", "Cannot open fontconfig file %s", path)              return          end          local raw = fh:read"*all" @@ -1479,12 +1599,22 @@ do --- closure for read_fonts_conf()          local confdata = lpegmatch(p_cheapxml, raw)          if not confdata then -            report("both", 3, "db", "cannot scan fontconfig file %s", path) +            report("both", 3, "db", "Cannot scan fontconfig file %s", path)              return          end          return confdata      end +    local p_conf   = P".conf" * P(-1) +    local p_filter = (1 - p_conf)^1 * p_conf + +    local conf_filter = function (path) +        if lpegmatch (p_filter, path) then +            return true +        end +        return false +    end +      --[[doc--           read_fonts_conf_indeed() is called with six arguments; the           latter three are tables that represent the state and are @@ -1545,7 +1675,7 @@ do --- closure for read_fonts_conf()                                  path, home, xdg_home,                                  acc,  done, dirs_done)                  elseif lfsisdir(path) then --- arrow code ahead -                    local config_files = dirglob(filejoin(path, "*.conf")) +                    local config_files = find_files (path, conf_filter)                      for _, filename in next, config_files do                          if not done[filename] then                              acc = read_fonts_conf_indeed( @@ -1645,7 +1775,7 @@ end  --- dbobj -> dbobj  local gen_fast_lookups = function (fontnames) -    report("both", 2, "db", "creating filename map") +    report("both", 2, "db", "Creating filename map")      local mappings   = fontnames.mappings      local nmappings  = #mappings      --- this is needlessly complicated due to texmf priorization @@ -1685,7 +1815,7 @@ local gen_fast_lookups = function (fontnames)              local known = filenames.base[base] or filenames.bare[bare]              if known then --- known                  report("both", 3, "db", -                       "font file “%s” already indexed (%d)", +                       "Font file “%s” already indexed (%d)",                         base, idx)                  report("both", 3, "db", "> old location: %s",                         (filenames.full[known] or "texmf")) @@ -1704,7 +1834,7 @@ local gen_fast_lookups = function (fontnames)      end      if config.luaotfload.prioritize == "texmf" then -        report("both", 2, "db", "preferring texmf fonts") +        report("both", 2, "db", "Preferring texmf fonts")          addmap(sys)          addmap(texmf)      else --- sys @@ -1783,7 +1913,7 @@ end  local ensure_names_path = function ( )      local path = names.path.dir      if not lfsisdir(path) then -        dirmkdirs(path) +        lfsmkdirs(path)      end      return path  end @@ -1829,6 +1959,8 @@ save_names = function (fontnames)                  os.remove(lucname)                  caches.compile(fontnames, luaname, lucname)                  report("info", 1, "db", "Font names database saved") +                report("info", 3, "db", "Text: " .. luaname) +                report("info", 3, "db", "Byte: " .. lucname)                  return true              end          end @@ -1875,7 +2007,7 @@ local purge_from_cache = function (category, path, list, all)                      local checkname = file.replacesuffix(                          filename, "lua", "luc")                      if lfs.isfile(checkname) then -                        report("info", 5, "cache", "removing %s", filename) +                        report("info", 5, "cache", "Removing %s", filename)                          os.remove(filename)                          n = n + 1                      end @@ -1883,15 +2015,17 @@ local purge_from_cache = function (category, path, list, all)              end          end      end -    report("info", 2, "cache", "removed lua files : %i", n) +    report("info", 2, "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 = dirglob(path .. "/**/*") +        local all = find_files (path) +          local luanames, lucnames, rest = { }, { }, { }          return collect_cache(nil, all, 1, luanames, lucnames, rest)      end @@ -1911,9 +2045,34 @@ local collect_cache collect_cache = function (path, all, n, luanames,      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 = caches.getwritablepath +                        (config.luaotfload.cache_dir) +    if writable then +        return writable +    end +end + +local getreadablecachepaths = function ( ) +    local readables = caches.getreadablepaths +                        (config.luaotfload.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 = caches.getwritablepath() +    local writable_path = getwritablecachepath ()      local luanames, lucnames, rest = collect_cache(writable_path)      if logs.get_loglevel() > 1 then          print_cache("writable path", writable_path, luanames, lucnames, rest) @@ -1924,7 +2083,7 @@ end  --- unit -> unit  local erase_cache = function ( ) -    local writable_path = caches.getwritablepath() +    local writable_path = getwritablecachepath ()      local luanames, lucnames, rest, all = collect_cache(writable_path)      if logs.get_loglevel() > 1 then          print_cache("writable path", writable_path, luanames, lucnames, rest) @@ -1939,19 +2098,20 @@ end  --- unit -> unit  local show_cache = function ( ) -    local readable_paths = caches.getreadablepaths() -    local writable_path  = caches.getwritablepath() +    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) +    separator () +    print_cache ("writable path", writable_path, +                 luanames, lucnames, rest)      texiowrite_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) +            local luanames, lucnames = collect_cache (readable_path) +            print_cache ("readable path", +                         readable_path, luanames, lucnames, rest)          end      end      separator() @@ -1971,6 +2131,7 @@ 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_string             = sanitize_string  names.getfilename                 = resolve_fullpath  --- font cache diff --git a/luaotfload-features.lua b/luaotfload-features.lua index 81d1437..690a33c 100644 --- a/luaotfload-features.lua +++ b/luaotfload-features.lua @@ -1032,7 +1032,7 @@ local feature_expr      = ws * Cg(assignment + switch) * ws  local option            = feature_expr  local feature_list      = Cf(Ct""                             * option -                           * (featuresep * option)^0 +                           * (featuresep * option^-1)^0                             , rawset)                          * featuresep^-1 diff --git a/luaotfload-lib-dir.lua b/luaotfload-lib-dir.lua deleted file mode 100644 index 3d0576e..0000000 --- a/luaotfload-lib-dir.lua +++ /dev/null @@ -1,470 +0,0 @@ -if not modules then modules = { } end modules ['l-dir'] = { -    version   = 1.001, -    comment   = "companion to luat-lib.mkiv", -    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL", -    copyright = "PRAGMA ADE / ConTeXt Development Team", -    license   = "see context related readme files" -} - --- dir.expandname will be merged with cleanpath and collapsepath - -local type, select = type, select -local find, gmatch, match, gsub = string.find, string.gmatch, string.match, string.gsub -local concat, insert, remove, unpack = table.concat, table.insert, table.remove, table.unpack -local lpegmatch = lpeg.match - -local P, S, R, C, Cc, Cs, Ct, Cv, V = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Cc, lpeg.Cs, lpeg.Ct, lpeg.Cv, lpeg.V - -dir = dir or { } -local dir = dir -local lfs = lfs - -local attributes = lfs.attributes -local walkdir    = lfs.dir -local isdir      = lfs.isdir -local isfile     = lfs.isfile -local currentdir = lfs.currentdir -local chdir      = lfs.chdir - --- in case we load outside luatex - -if not isdir then -    function isdir(name) -        local a = attributes(name) -        return a and a.mode == "directory" -    end -    lfs.isdir = isdir -end - -if not isfile then -    function isfile(name) -        local a = attributes(name) -        return a and a.mode == "file" -    end -    lfs.isfile = isfile -end - --- handy - -function dir.current() -    return (gsub(currentdir(),"\\","/")) -end - --- optimizing for no find (*) does not save time - ---~ local function globpattern(path,patt,recurse,action) -- fails in recent luatex due to some change in lfs ---~     local ok, scanner ---~     if path == "/" then ---~         ok, scanner = xpcall(function() return walkdir(path..".") end, function() end) -- kepler safe ---~     else ---~         ok, scanner = xpcall(function() return walkdir(path)      end, function() end) -- kepler safe ---~     end ---~     if ok and type(scanner) == "function" then ---~         if not find(path,"/$") then path = path .. '/' end ---~         for name in scanner do ---~             local full = path .. name ---~             local mode = attributes(full,'mode') ---~             if mode == 'file' then ---~                 if find(full,patt) then ---~                     action(full) ---~                 end ---~             elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then ---~                 globpattern(full,patt,recurse,action) ---~             end ---~         end ---~     end ---~ end - -local lfsisdir = isdir - -local function isdir(path) -    path = gsub(path,"[/\\]+$","") -    return lfsisdir(path) -end - -lfs.isdir = isdir - -local function globpattern(path,patt,recurse,action) -    if path == "/" then -        path = path .. "." -    elseif not find(path,"/$") then -        path = path .. '/' -    end -    if isdir(path) then -- lfs.isdir does not like trailing / -        for name in walkdir(path) do -- lfs.dir accepts trailing / -            local full = path .. name -            local mode = attributes(full,'mode') -            if mode == 'file' then -                if find(full,patt) then -                    action(full) -                end -            elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then -                globpattern(full,patt,recurse,action) -            end -        end -    end -end - -dir.globpattern = globpattern - -local function collectpattern(path,patt,recurse,result) -    local ok, scanner -    result = result or { } -    if path == "/" then -        ok, scanner, first = xpcall(function() return walkdir(path..".") end, function() end) -- kepler safe -    else -        ok, scanner, first = xpcall(function() return walkdir(path)      end, function() end) -- kepler safe -    end -    if ok and type(scanner) == "function" then -        if not find(path,"/$") then path = path .. '/' end -        for name in scanner, first do -            local full = path .. name -            local attr = attributes(full) -            local mode = attr.mode -            if mode == 'file' then -                if find(full,patt) then -                    result[name] = attr -                end -            elseif recurse and (mode == "directory") and (name ~= '.') and (name ~= "..") then -                attr.list = collectpattern(full,patt,recurse) -                result[name] = attr -            end -        end -    end -    return result -end - -dir.collectpattern = collectpattern - -local pattern = Ct { -    [1] = (C(P(".") + P("/")^1) + C(R("az","AZ") * P(":") * P("/")^0) + Cc("./")) * V(2) * V(3), -    [2] = C(((1-S("*?/"))^0 * P("/"))^0), -    [3] = C(P(1)^0) -} - -local filter = Cs ( ( -    P("**") / ".*" + -    P("*")  / "[^/]*" + -    P("?")  / "[^/]" + -    P(".")  / "%%." + -    P("+")  / "%%+" + -    P("-")  / "%%-" + -    P(1) -)^0 ) - -local function glob(str,t) -    if type(t) == "function" then -        if type(str) == "table" then -            for s=1,#str do -                glob(str[s],t) -            end -        elseif isfile(str) then -            t(str) -        else -            local split = lpegmatch(pattern,str) -- we could use the file splitter -            if split then -                local root, path, base = split[1], split[2], split[3] -                local recurse = find(base,"%*%*") -                local start = root .. path -                local result = lpegmatch(filter,start .. base) -                globpattern(start,result,recurse,t) -            end -        end -    else -        if type(str) == "table" then -            local t = t or { } -            for s=1,#str do -                glob(str[s],t) -            end -            return t -        elseif isfile(str) then -            if t then -                t[#t+1] = str -                return t -            else -                return { str } -            end -        else -            local split = lpegmatch(pattern,str) -- we could use the file splitter -            if split then -                local t = t or { } -                local action = action or function(name) t[#t+1] = name end -                local root, path, base = split[1], split[2], split[3] -                local recurse = find(base,"%*%*") -                local start = root .. path -                local result = lpegmatch(filter,start .. base) -                globpattern(start,result,recurse,action) -                return t -            else -                return { } -            end -        end -    end -end - -dir.glob = glob - ---~ list = dir.glob("**/*.tif") ---~ list = dir.glob("/**/*.tif") ---~ list = dir.glob("./**/*.tif") ---~ list = dir.glob("oeps/**/*.tif") ---~ list = dir.glob("/oeps/**/*.tif") - -local function globfiles(path,recurse,func,files) -- func == pattern or function -    if type(func) == "string" then -        local s = func -        func = function(name) return find(name,s) end -    end -    files = files or { } -    local noffiles = #files -    for name in walkdir(path) do -        if find(name,"^%.") then -            --- skip -        else -            local mode = attributes(name,'mode') -            if mode == "directory" then -                if recurse then -                    globfiles(path .. "/" .. name,recurse,func,files) -                end -            elseif mode == "file" then -                if not func or func(name) then -                    noffiles = noffiles + 1 -                    files[noffiles] = path .. "/" .. name -                end -            end -        end -    end -    return files -end - -dir.globfiles = globfiles - --- t = dir.glob("c:/data/develop/context/sources/**/????-*.tex") --- t = dir.glob("c:/data/develop/tex/texmf/**/*.tex") --- t = dir.glob("c:/data/develop/context/texmf/**/*.tex") --- t = dir.glob("f:/minimal/tex/**/*") --- print(dir.ls("f:/minimal/tex/**/*")) --- print(dir.ls("*.tex")) - -function dir.ls(pattern) -    return concat(glob(pattern),"\n") -end - ---~ mkdirs("temp") ---~ mkdirs("a/b/c") ---~ mkdirs(".","/a/b/c") ---~ mkdirs("a","b","c") - -local make_indeed = true -- false - -local onwindows = os.type == "windows" or find(os.getenv("PATH"),";") - -if onwindows then - -    function dir.mkdirs(...) -        local str, pth = "", "" -        for i=1,select("#",...) do -            local s = select(i,...) -            if s == "" then -                -- skip -            elseif str == "" then -                str = s -            else -                str = str .. "/" .. s -            end -        end -        local first, middle, last -        local drive = false -        first, middle, last = match(str,"^(//)(//*)(.*)$") -        if first then -            -- empty network path == local path -        else -            first, last = match(str,"^(//)/*(.-)$") -            if first then -                middle, last = match(str,"([^/]+)/+(.-)$") -                if middle then -                    pth = "//" .. middle -                else -                    pth = "//" .. last -                    last = "" -                end -            else -                first, middle, last = match(str,"^([a-zA-Z]:)(/*)(.-)$") -                if first then -                    pth, drive = first .. middle, true -                else -                    middle, last = match(str,"^(/*)(.-)$") -                    if not middle then -                        last = str -                    end -                end -            end -        end -        for s in gmatch(last,"[^/]+") do -            if pth == "" then -                pth = s -            elseif drive then -                pth, drive = pth .. s, false -            else -                pth = pth .. "/" .. s -            end -            if make_indeed and not isdir(pth) then -                lfs.mkdir(pth) -            end -        end -        return pth, (isdir(pth) == true) -    end - -    --~ print(dir.mkdirs("","","a","c")) -    --~ print(dir.mkdirs("a")) -    --~ print(dir.mkdirs("a:")) -    --~ print(dir.mkdirs("a:/b/c")) -    --~ print(dir.mkdirs("a:b/c")) -    --~ print(dir.mkdirs("a:/bbb/c")) -    --~ print(dir.mkdirs("/a/b/c")) -    --~ print(dir.mkdirs("/aaa/b/c")) -    --~ print(dir.mkdirs("//a/b/c")) -    --~ print(dir.mkdirs("///a/b/c")) -    --~ print(dir.mkdirs("a/bbb//ccc/")) - -else - -    function dir.mkdirs(...) -        local str, pth = "", "" -        for i=1,select("#",...) do -            local s = select(i,...) -            if s and s ~= "" then -- we catch nil and false -                if str ~= "" then -                    str = str .. "/" .. s -                else -                    str = s -                end -            end -        end -        str = gsub(str,"/+","/") -        if find(str,"^/") then -            pth = "/" -            for s in gmatch(str,"[^/]+") do -                local first = (pth == "/") -                if first then -                    pth = pth .. s -                else -                    pth = pth .. "/" .. s -                end -                if make_indeed and not first and not isdir(pth) then -                    lfs.mkdir(pth) -                end -            end -        else -            pth = "." -            for s in gmatch(str,"[^/]+") do -                pth = pth .. "/" .. s -                if make_indeed and not isdir(pth) then -                    lfs.mkdir(pth) -                end -            end -        end -        return pth, (isdir(pth) == true) -    end - -    --~ print(dir.mkdirs("","","a","c")) -    --~ print(dir.mkdirs("a")) -    --~ print(dir.mkdirs("/a/b/c")) -    --~ print(dir.mkdirs("/aaa/b/c")) -    --~ print(dir.mkdirs("//a/b/c")) -    --~ print(dir.mkdirs("///a/b/c")) -    --~ print(dir.mkdirs("a/bbb//ccc/")) - -end - -dir.makedirs = dir.mkdirs - --- we can only define it here as it uses dir.current - -if onwindows then - -    function dir.expandname(str) -- will be merged with cleanpath and collapsepath -        local first, nothing, last = match(str,"^(//)(//*)(.*)$") -        if first then -            first = dir.current() .. "/" -- dir.current sanitizes -        end -        if not first then -            first, last = match(str,"^(//)/*(.*)$") -        end -        if not first then -            first, last = match(str,"^([a-zA-Z]:)(.*)$") -            if first and not find(last,"^/") then -                local d = currentdir() -                if chdir(first) then -                    first = dir.current() -                end -                chdir(d) -            end -        end -        if not first then -            first, last = dir.current(), str -        end -        last = gsub(last,"//","/") -        last = gsub(last,"/%./","/") -        last = gsub(last,"^/*","") -        first = gsub(first,"/*$","") -        if last == "" or last == "." then -            return first -        else -            return first .. "/" .. last -        end -    end - -else - -    function dir.expandname(str) -- will be merged with cleanpath and collapsepath -        if not find(str,"^/") then -            str = currentdir() .. "/" .. str -        end -        str = gsub(str,"//","/") -        str = gsub(str,"/%./","/") -        str = gsub(str,"(.)/%.$","%1") -        return str -    end - -end - -file.expandname = dir.expandname -- for convenience - -local stack = { } - -function dir.push(newdir) -    insert(stack,currentdir()) -    if newdir and newdir ~= "" then -        chdir(newdir) -    end -end - -function dir.pop() -    local d = remove(stack) -    if d then -        chdir(d) -    end -    return d -end - -local function found(...) -- can have nil entries -    for i=1,select("#",...) do -        local path = select(i,...) -        local kind = type(path) -        if kind == "string" then -            if isdir(path) then -                return path -            end -        elseif kind == "table" then -            -- here we asume no holes, i.e. an indexed table -            local path = found(unpack(path)) -            if path then -                return path -            end -        end -    end - -- return nil -- if we want print("crappath") to show something -end - -dir.found = found diff --git a/luaotfload-merged.lua b/luaotfload-merged.lua index 81e3fec..52c199a 100644 --- a/luaotfload-merged.lua +++ b/luaotfload-merged.lua @@ -3044,10 +3044,10 @@ if not caches.namespace or caches.namespace=="" or caches.namespace=="context" t  end  do    local cachepaths=kpse.expand_var('$TEXMFCACHE') or "" -  if cachepaths=="" then +  if cachepaths=="" or cachepaths == "$TEXMFCACHE" then      cachepaths=kpse.expand_var('$TEXMFVAR') or ""    end -  if cachepaths=="" then +  if cachepaths=="" or cachepaths == "$TEXMFVAR" then      cachepaths=kpse.expand_var('$VARTEXMF') or ""    end    if cachepaths=="" then @@ -3143,7 +3143,7 @@ function caches.savedata(path,name,data)    local luaname,lucname=makefullname(path,name)    if luaname then      texio.write(string.format("(save: %s)",luaname)) -    table.tofile(luaname,data,true,{ reduce=true }) +    table.tofile(luaname,data,true,{ reduce=false })      if lucname and type(caches.compile)=="function" then        os.remove(lucname)         texio.write(string.format("(save: %s)",lucname)) diff --git a/luaotfload-override.lua b/luaotfload-override.lua index caf3627..39cc172 100644 --- a/luaotfload-override.lua +++ b/luaotfload-override.lua @@ -15,17 +15,23 @@ because we lack a user interface to toggle per-subsystem tracing.  local module_name       = "luaotfload" +local ioopen            = io.open +local iowrite           = io.write +local lfsisdir          = lfs.isdir +local lfsisfile         = lfs.isfile +local md5sumhexa        = md5.sumhexa +local osdate            = os.date +local ostime            = os.time  local select            = select  local stringformat      = string.format +local stringsub         = string.sub  local tableconcat       = table.concat +local texio_write_nl    = texio.write_nl  local texiowrite_nl     = texio.write_nl +local texio_write       = texio.write  local texiowrite        = texio.write  local type              = type -local texio_write_nl    = texio.write_nl -local texio_write       = texio.write -local iowrite           = io.write -  --[[doc--  We recreate the verbosity levels previously implemented in font-nms: @@ -62,9 +68,67 @@ logs.getloglevel    = get_loglevel  logs.get_loglevel   = get_loglevel  logs.get_log_level  = get_loglevel -local set_logout = function (s) +local writeln --- scope so we can change it + +local log_msg = [[ +logging output redirected to %s +to monitor the progress run "tail -f %s" in another terminal +]] + +local tmppath = os.getenv "TMPDIR" or "/tmp" + +local choose_logfile = function ( ) +    if lfsisdir (tmppath) then +        local fname +        repeat --- ensure that file of that name doesn’t exist +            fname = tmppath .. "/luaotfload-log-" +                            .. stringsub (md5sumhexa (ostime ()), 1, 8) +        until not lfsisfile (fname) +        iowrite (stringformat (log_msg, fname, fname)) +        return ioopen (fname, "w") +    end +    --- missing /tmp +    return false +end + +local set_logout = function (s, finalizers)      if s == "stdout" then          logout = "term" +    elseif s == "file" then --- inject custom logger +        local chan = choose_logfile () +        chan:write (stringformat ("logging initiated at %s", +                                  osdate ("%F %T", ostime ()))) +        local writefile = function (...) +            if select ("#", ...) == 2 then +                chan:write (select (2, ...)) +            else +                chan:write (select (1, ...)) +            end +        end +        local writefile_nl= function (...) +            chan:write "\n" +            if select ("#", ...) == 2 then +                chan:write (select (2, ...)) +            else +                chan:write (select (1, ...)) +            end +        end + +        local writeln_orig = writeln + +        texiowrite    = writefile +        texiowrite_nl = writefile_nl +        writeln       = writefile_nl + +        finalizers[#finalizers+1] = function () +            chan:write (stringformat ("\nlogging finished at %s\n", +                                      osdate ("%F %T", ostime ()))) +            chan:close () +            texiowrite    = texio.write +            texiowrite_nl = texio.write_nl +            writeln       = writeln_orig +        end +        return finalizers      --else --- remains “log”      end  end @@ -92,7 +156,6 @@ end  io.stdout:setvbuf "no"  io.stderr:setvbuf "no" -local writeln  if tex and (tex.jobname or tex.formatname) then      --- TeX      writeln = texiowrite_nl @@ -106,7 +169,10 @@ end  stdout = function (category, ...)      local res = { module_name, "|", category, ":" } -    if select("#", ...) == 1 then +    local nargs = select("#", ...) +    if nargs == 0 then +        writeln (tableconcat ({...})) +    elseif nargs == 1 then          res[#res+1] = select(1, ...) -- around 30% faster than unpack()      else          res[#res+1] = stringformat(...) @@ -152,7 +218,7 @@ local names_report = function (mode, lvl, ...)      if loglevel >= lvl then          if mode == "log" then              log (...) -        elseif mode == "both" then +        elseif mode == "both" and log ~= "stdout" then              log (...)              stdout (...)          else diff --git a/luaotfload-tool.lua b/luaotfload-tool.lua index f1302a7..a353b37 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" @@ -79,9 +80,13 @@ After support for querying the database was added, the latter appeared  to be the more appropriate.  --doc]]-- -config              = config or { } -local config        = config -config.luaotfload   = config.luaotfload or { } +config                        = config or { } +local config                  = config +config.luaotfload             = config.luaotfload or { } +config.luaotfload.names_dir   = config.luaotfload.names_dir or "names" +config.luaotfload.cache_dir   = config.luaotfload.cache_dir or "fonts" +config.luaotfload.names_file  = config.luaotfload.names_file +                             or "luaotfload-names.lua"  do -- we don’t have file.basename and the likes yet, so inline parser ftw      local slash        = P"/" @@ -108,19 +113,6 @@ config.lualibs.load_extended    = false  require "lualibs" ---- prepare directories: the cache function in Luatex-Fonts ---- checks for writable directory only on startup, so everything ---- has to be laid out before we load basics-gen - -local cachepath = kpse.expand_var "$TEXMFVAR" -if not lfs.isdir(cachepath) then -    dir.mkdirs(cachepath) -    if not lfs.isdir(cachepath) then -        texiowrite_nl(stringformat( -            "ERROR could not create directory %s", cachepath)) -    end -end -  --[[doc--  \fileent{luatex-basics-gen.lua} calls functions from the  \luafunction{texio.*} library; too much for our taste. @@ -141,8 +133,13 @@ require"alt_getopt"  local version  = "2.3" -- same version number as luaotfload  local names    = fonts.names -local db_src_out = names.path.dir.."/"..names.path.basename -local db_bin_out = file.replacesuffix(db_src_out, "luc") +local sanitize_string = names.sanitize_string + +--local db_src_out = names.path.dir.."/"..names.path.basename +local names_plain = file.join +    (caches.getwritablepath (config.luaotfload.names_dir), +     config.luaotfload.names_file) +local names_bin   = file.replacesuffix (names_plain, "luc")  local help_messages = {      ["luaotfload-tool"] = [[ @@ -181,7 +178,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> @@ -198,6 +199,9 @@ The font database will be saved to    --cache=<directive>          operate on font cache, where <directive> is                                 “show”, “purge”, or “erase” +The font cache will be written to +   %s +  ]],      mkluatexfontdb = [[ @@ -226,7 +230,12 @@ 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, +                               names_plain, +                               names_bin, +                               caches.getwritablepath +                                (config.luaotfload.cache_dir)))  end  local version_msg = function ( ) @@ -235,34 +244,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 -local show_font_info = function (filename) -    local fullname = resolvers.findfile(filename) +--- 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 + +--[[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 +647,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 +735,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 +830,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 +845,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] @@ -540,6 +901,22 @@ actions.list = function (job)      return true, true  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.  mkluatexfontdb.lua relies on the script alt_getopt to process argv and @@ -553,6 +930,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 +948,7 @@ local process_cmdline = function ( ) -- unit -> jobspec          fuzzy              = "F",          help               = "h",          info               = "i", +        inspect            = "I",          limit              = 1,          list               = 1,          log                = 1, @@ -578,9 +958,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,12 +989,18 @@ 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 -                logs.set_logout(str) +                finalizers = logs.set_logout(str, finalizers)              end          elseif v == "find" then              action_pending["query"] = true @@ -627,6 +1014,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 +1057,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,22 +1065,26 @@ 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      end -    texiowrite_nl"" +    if finalize () == false then +        retval = -1 +    end + +    --texiowrite_nl""      return retval  end diff --git a/luaotfload-tool.rst b/luaotfload-tool.rst index 9b03b37..31a1010 100644 --- a/luaotfload-tool.rst +++ b/luaotfload-tool.rst @@ -15,11 +15,11 @@  SYNOPSIS  ======================================================================= -**luaotfload** [ -bDcfFipquvVh ] +**luaotfload** [ -bDcfFiIpquvVwh ]  **luaotfload** --update [ --force ] [ --quiet ] [ --verbose ] [ --prefer-texmf ] [ --dry-run ] -**luaotfload** --find=FONTNAME [ --fuzzy ] [ --info ] +**luaotfload** --find=FONTNAME [ --fuzzy ] [ --info ] [ --inspect ]  **luaotfload** --flush-lookups @@ -71,8 +71,18 @@ query mode  --fuzzy, -F             Show approximate matches to the file name if                          the lookup was unsuccessful (requires                          ``--find``). +  --info, -i              Display basic information to a resolved font                          file (requires ``--find``). +--inspect, -I           Display detailed information by loading the +                        font and analyzing the font table; very slow! +                        For the meaning of the returned fields see +                        the LuaTeX documentation. +                        (requires ``--find``). +--warnings, -w          Print the warnings generated by the fontloader +                        library (assumes ``-I``). Automatically enabled +                        if the verbosity level exceeds 2. +  --show-blacklist, -b    Show blacklisted files (not directories).  --list=CRITERION        Show entries, where *CRITERION* is one of the                          following: @@ -106,8 +116,14 @@ miscellaneous  --verbose=N, -v         Set verbosity level to *n* or the number of                          repetitions of ``-v``.  --quiet                 No verbose output (log level set to zero). ---log=stdout            Redirect log output to terminal (for database -                        troubleshooting). +--log=CHANNEL           Redirect log output (for database +                        troubleshooting), where *CHANNEL* can be + +                        1) ``stdout`` -> all output will be +                           dumped to the terminal; or +                        2) ``file`` -> write to a file to the temporary +                           directory (the name will be chosen +                           automatically (**experimental!**).  --version, -V           Show version number and exit.  --help, -h              Show help message and exit. diff --git a/luaotfload.dtx b/luaotfload.dtx index 70edf8c..b8bee75 100644 --- a/luaotfload.dtx +++ b/luaotfload.dtx @@ -40,7 +40,7 @@  \input docstrip.tex  \Msg{************************************************************************}  \Msg{* Installation} -\Msg{* Package: luaotfload v2.2 OpenType layout system} +\Msg{* Package: luaotfload v2.3 OpenType layout system}  \Msg{************************************************************************}  \keepsilent @@ -111,7 +111,7 @@ and the derived files  %<*driver>  \NeedsTeXFormat{LaTeX2e}  \ProvidesFile{luaotfload.drv}% -  [2013/05/23 v2.2d OpenType layout system]% +  [2013/05/23 v2.3d OpenType layout system]%  \documentclass{ltxdoc}  \usepackage{metalogo,multicol,mdwlist,fancyvrb,xspace}  \usepackage[x11names]{xcolor} @@ -231,7 +231,7 @@ and the derived files  % \GetFileInfo{luaotfload.drv}  %  % \title{The \identifier{luaotfload} package} -% \date{2013/05/23 v2.2d} +% \date{2013/05/23 v2.3d}  % \author{Elie Roux · Khaled Hosny · Philipp Gesang\\  %         Home:      \url{https://github.com/lualatex/luaotfload}\\  %         Support:   \email{lualatex-dev@tug.org}} @@ -400,7 +400,23 @@ and the derived files  % \begin{quote}  %   |\font\fontname=|\meta{prefix}|:|\meta{fontname}\dots  % \end{quote} -% where \meta{prefix} is either \verb|file:| or \verb|name:|. +% where \meta{prefix} is either \verb|file:| or \verb|name:|.\footnote{% +%   The development version also knows two further prefixes, +%   \verb|kpse:| and \verb|my:|. +%   A \verb|kpse| lookup is restricted to files that can be found by +%   \identifier{kpathsea} and +%   will not attempt to locate system fonts. +%   This behavior can be of value when an extra degree of encapsulation is +%   needed, for instance when supplying a customized tex distribution. +% +%   The \verb|my| lookup takes this a step further: it lets you define +%   a custom resolver function and hook it into the \luafunction{resolve_font} +%   callback. +%   This ensures full control over how a file is located. +%   For a working example see the +%   \href{https://bitbucket.org/phg/lua-la-tex-tests/src/5f6a535d/pln-lookup-callback-1.tex} +%        {test repo}. +% }  % It determines whether the font loader should interpret the request as  % a \emphasis{file name} or  %   \emphasis{font name}, respectively, @@ -410,7 +426,26 @@ and the derived files  %             “GFS Bodoni Rg”, and  %             “PT Serif Caption”  % -- they are the human readable identifiers -% usually listed in drop-down menus and the like. +% usually listed in drop-down menus and the like.\footnote{% +%   Font names may appear like a great choice at first because they +%   offer seemingly more intuitive identifiers in comparison to arguably +%   cryptic file names: +%   “PT Sans Bold” is a lot more descriptive than \fileent{PTS75F.ttf}. +%   On the other hand, font names are quite arbitrary and there is no +%   universal method to determine their meaning. +%   While \identifier{luaotfload} provides fairly sophisticated heuristic +%   to figure out a matching font style, weight, and optical size, it +%   cannot be relied upon to work satisfactorily for all font files. +%   For an in-depth analysis of the situation and how broken font names +%   are, please refer to +%   \href{http://www.ntg.nl/pipermail/ntg-context/2013/073889.html} +%        {this post} +%   by Hans Hagen, the author of the font loader. +%   If in doubt, use filenames. +%   \fileent{luaotfload-tool} can perform the matching for you with the +%   option \verb|--find=<name>|, and you can use the file name it returns +%   in your font definition. +% }  % In order for fonts installed both in system locations and in your  % \fileent{texmf} to be accessible by font name, \identifier{luaotfload} must  % first collect the metadata included in the files. @@ -1159,9 +1194,6 @@ and the derived files  %     \ouritem {luaotfload-features.lua}   font feature handling;  %                                          incorporates some of the code from  %                                          \fileent{font-otc} from \CONTEXT; -%     \ouritem {luaotfload-lib-dir.lua}    \fileent{l-dir} from \CONTEXT; -%                                          contains functionality required -%                                          by \fileent{luaotfload-font-nms.lua}.  %     \ouritem {luaotfload-override.lua}   overrides the \CONTEXT logging  %                                          functionality.  %     \ouritem {luaotfload-loaders.lua}    registers the \OpenType @@ -1428,8 +1460,8 @@ and the derived files  %<*lua>  % \fi  %    \begin{macrocode} -luaotfload                  = luaotfload or {} -local luaotfload            = luaotfload +luaotfload                        = luaotfload or {} +local luaotfload                  = luaotfload  config                            = config or { }  config.luaotfload                 = config.luaotfload or { } @@ -1440,7 +1472,9 @@ config.luaotfload.compatibility   = config.luaotfload.compatibility    or false  config.luaotfload.loglevel        = config.luaotfload.loglevel         or 1  config.luaotfload.color_callback  = config.luaotfload.color_callback   or "pre_linebreak_filter"  config.luaotfload.prioritize      = config.luaotfload.prioritize       or "sys" ---luaotfload.prefer_merge     = config.luaotfload.prefer_merge or true +config.luaotfload.names_dir       = config.luaotfload.names_dir        or "names" +config.luaotfload.cache_dir       = config.luaotfload.cache_dir        or "fonts" +config.luaotfload.names_file      = config.luaotfload.names_file       or "luaotfload-names.lua"  luaotfload.module = {      name          = "luaotfload", @@ -1770,7 +1804,6 @@ add_to_callback("hpack_filter",  add_to_callback("find_vf_file",                  find_vf_file, "luaotfload.find_vf_file") -loadmodule"lib-dir.lua"    --- required by luaofload-database.lua  loadmodule"override.lua"   --- “luat-ovr”  logs.set_loglevel(config.luaotfload.loglevel) @@ -2036,7 +2069,7 @@ loadmodule"auxiliary.lua"   --- additionaly high-level functionality (new)  \else    \NeedsTeXFormat{LaTeX2e}    \ProvidesPackage{luaotfload}% -    [2013/05/23 v2.2d OpenType layout system] +    [2013/05/23 v2.3d OpenType layout system]    \RequirePackage{luatexbase}  \fi  \ifnum\luatexversion<76 | 
