diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | filegraph.dot | 44 | ||||
| -rwxr-xr-x | fontdbutil.lua | 34 | ||||
| -rw-r--r-- | luaotfload-basics-gen.lua | 48 | ||||
| -rw-r--r-- | luaotfload-database.lua | 862 | ||||
| -rw-r--r-- | luaotfload-features.lua | 74 | ||||
| -rw-r--r-- | luaotfload-merged.lua | 856 | ||||
| -rw-r--r-- | luaotfload-override.lua | 3 | ||||
| -rw-r--r-- | luaotfload.dtx | 75 | ||||
| -rw-r--r-- | tests/lookups.tex | 6 | 
10 files changed, 1545 insertions, 459 deletions
@@ -31,3 +31,5 @@ tests/*.dvi  tests/*.ofm  tests/*.ovp  tests/*.ovf +tests/*.sty +tests/luaotfload* diff --git a/filegraph.dot b/filegraph.dot index f1283f0..a0eadec 100644 --- a/filegraph.dot +++ b/filegraph.dot @@ -27,6 +27,9 @@ strict digraph luaotfload_files { //looks weird with circo ...  /* ····································································   * file structure   * ································································· */ +    fontdbutil  -> font_names   [label="--update", +                                 style=dashed] +      luaotfload -> otfl_fonts_merged [label="merged"]      luaotfload -> merged_lua_libs     [label="unmerged", style=solid]      luaotfload -> merged_luatex_fonts [label="unmerged", style=solid] @@ -35,7 +38,6 @@ strict digraph luaotfload_files { //looks weird with circo ...      luaotfload -> luaotfload_libs      luaotfload -> otfl_blacklist_cnf -      otfl_fonts_merged -> merged_lua_libs     [label="merged",                                                style=dotted,                                                lhead=cluster_merged] @@ -46,12 +48,36 @@ strict digraph luaotfload_files { //looks weird with circo ...                                                style=dotted,                                                lhead=cluster_merged] +    merged_luatex_fonts -> font_age [label="luatex-fonts-enc.lua", +                                     ltail=cluster_merged] + +    luaotfload_libs -> font_names [label="luaotfload-database.lua"] +    mkglyphlist -> font_age     [label="generates from glyphlist.txt", +                                 style=dashed] + +    subgraph { rank = same; mkglyphlist; fontdbutil; luaotfload }  /* ····································································   * main files   * ································································· */ +    fontdbutil        [label  = "fontdbutil\nmkluatexfontdb.lua", +                       shape  = rect, +                       width  = "3.2cm", +                       height = "1.2cm", +                       color  = "#01012222", +                       style  = "filled,rounded", +                       penwidth=2] + +    mkglyphlist       [label  = "mkglyphlist", +                       shape  = rect, +                       width  = "3.2cm", +                       height = "1.2cm", +                       color  = "#01012222", +                       style  = "filled,rounded", +                       penwidth=2] +      luaotfload        [label  = "luaotfload.lua",                         shape  = rect,                         width  = "3.2cm", @@ -81,6 +107,22 @@ strict digraph luaotfload_files { //looks weird with circo ...   * ································································· */ +    font_age [style      = "filled,dashed", +              shape      = rect, +              width      = "3.2cm", +              fillcolor  = "#01012222", +              color      = grey40, +              style      = "filled,dotted,rounded", +              label      = "font-age.lua"] + +    font_names [style      = "filled,dashed", +                shape      = rect, +                width      = "3.2cm", +                fillcolor  = "#01012222", +                color      = grey40, +                style      = "filled,dotted,rounded", +                label      = "luaotfload-names.lua\nluaotfload-names.luc"] +      otfl_blacklist_cnf [style      = "filled,dashed",                          shape      = rect,                          width      = "3.2cm", diff --git a/fontdbutil.lua b/fontdbutil.lua index 31c7dfa..fed1840 100755 --- a/fontdbutil.lua +++ b/fontdbutil.lua @@ -1,11 +1,15 @@  #!/usr/bin/env texlua ---[[ + +--[[doc--  This file was originally written by Elie Roux and Khaled Hosny and is under CC0  license (see http://creativecommons.org/publicdomain/zero/1.0/legalcode). -This file is a wrapper for the luaotfload's font names module. It is part of the -luaotfload bundle, please see the luaotfload documentation for more info. ---]] +This file is a wrapper for the luaotfload font names module +(luaotfload-database.lua). It is part of the luaotfload bundle, please +see the luaotfload documentation for more info. Report bugs to +\url{https://github.com/lualatex/luaotfload/issues}. + +--doc]]--  kpse.set_program_name"luatex" @@ -14,18 +18,17 @@ local texiowrite_nl   = texio.write_nl  local stringfind      = string.find  local stringlower     = string.lower --- First we need to be able to load module (code copied from --- luatexbase-loader.sty): +  local loader_file = "luatexbase.loader.lua"  local loader_path = assert(kpse.find_file(loader_file, "lua"),                             "File '"..loader_file.."' not found") +  string.quoted = string.quoted or function (str)    return string.format("%q",str)   end ---texiowrite_nl("("..loader_path..")") -dofile(loader_path) -- FIXME this pollutes stdout with filenames +dofile(loader_path)  --[[doc--  Depending on how the script is called we change its behavior. @@ -73,7 +76,20 @@ config.lualibs.prefer_merged    = true  config.lualibs.load_extended    = false  require"lualibs" + +--[[doc-- +\fileent{luatex-basics-gen.lua} calls functions from the +\luafunction{texio.*} library; too much for our taste. +We intercept them with dummies. +--doc]]-- + +local dummy_function = function ( ) end +local backup_write, backup_write_nl  = texio.write, texio.write_nl + +texio.write, texio.write_nl          = dummy_function, dummy_function  require"luaotfload-basics-gen.lua" +texio.write, texio.write_nl          = backup_write, backup_write_nl +  require"luaotfload-override.lua"  --- this populates the logs.* namespace  require"luaotfload-database"  require"alt_getopt" @@ -224,7 +240,7 @@ actions.generate = function (job)      local fontnames, savedname      fontnames = names.update(fontnames, job.force_reload)      logs.names_report("log", 0, "db", -        "fonts in the database", "%i", #fontnames.mappings) +        "Fonts in the database: %i", #fontnames.mappings)      savedname = names.save(fontnames)      if savedname then --- FIXME have names.save return bool          return true, true diff --git a/luaotfload-basics-gen.lua b/luaotfload-basics-gen.lua index 727086e..61f3910 100644 --- a/luaotfload-basics-gen.lua +++ b/luaotfload-basics-gen.lua @@ -130,7 +130,9 @@ end  caches = { } -local writable, readables = nil, { } +local writable  = nil +local readables = { } +local usingjit  = jit  if not caches.namespace or caches.namespace == "" or caches.namespace == "context" then      caches.namespace = 'generic' @@ -204,7 +206,7 @@ end  local function makefullname(path,name)      if path and path ~= "" then          name = "temp-" .. name -- clash prevention -        return file.addsuffix(file.join(path,name),"lua"), file.addsuffix(file.join(path,name),"luc") +        return file.addsuffix(file.join(path,name),"lua"), file.addsuffix(file.join(path,name),usingjit and "lub" or "luc")      end  end @@ -265,26 +267,36 @@ end  -- this) in which case one should limit the method to luac and enable support  -- for execution. -caches.compilemethod = "both" +-- function caches.compile(data,luaname,lucname) +--     local d = io.loaddata(luaname) +--     if not d or d == "" then +--         d = table.serialize(data,true) -- slow +--     end +--     if d and d ~= "" then +--         local f = io.open(lucname,'w') +--         if f then +--             local s = loadstring(d) +--             if s then +--                 f:write(string.dump(s,true)) +--             end +--             f:close() +--         end +--     end +-- end  function caches.compile(data,luaname,lucname) -    local done = false -    if caches.compilemethod == "luac" or caches.compilemethod == "both" then -        done = os.spawn("texluac -o " .. string.quoted(lucname) .. " -s " .. string.quoted(luaname)) == 0 +    local d = io.loaddata(luaname) +    if not d or d == "" then +        d = table.serialize(data,true) -- slow      end -    if not done and (caches.compilemethod == "dump" or caches.compilemethod == "both") then -        local d = io.loaddata(luaname) -        if not d or d == "" then -            d = table.serialize(data,true) -- slow -        end -        if d and d ~= "" then -            local f = io.open(lucname,'w') -            if f then -                local s -                if _G["loadstring"] then s=loadstring(d) else s=load(d) end -                f:write(string.dump(s)) -                f:close() +    if d and d ~= "" then +        local f = io.open(lucname,'w') +        if f then +            local s = loadstring(d) +            if s then +                f:write(string.dump(s,true))              end +            f:close()          end      end  end diff --git a/luaotfload-database.lua b/luaotfload-database.lua index 19b04db..d731038 100644 --- a/luaotfload-database.lua +++ b/luaotfload-database.lua @@ -18,6 +18,7 @@ local pcall                   = pcall  local require                 = require  local tonumber                = tonumber +local fontloaderinfo          = fontloader.info  local iolines                 = io.lines  local ioopen                  = io.open  local kpseexpand_path         = kpse.expand_path @@ -45,6 +46,8 @@ local utf8lower               = unicode.utf8.lower  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 fileextname             = file.extname  local fileiswritable          = file.iswritable @@ -81,32 +84,6 @@ end  names.path.dir = writable_path  names.path.path = filejoin(writable_path, names.path.basename) - ----- <FIXME> ---- ---- these lines load some binary module called “lualatex-platform” ---- that doesn’t appear to build with Lua 5.2. I’m going ahead and ---- disable it for the time being until someone clarifies what it ---- is supposed to do and whether we should care to fix it. ---- ---local success = pcall(require, "luatexbase.modutils") ---if success then ---   success = pcall(luatexbase.require_module, ---                   "lualatex-platform", "2011/03/30") ---    print(success) ---end - ---local get_installed_fonts ---if success then ---   get_installed_fonts = lualatex.platform.get_installed_fonts ---else ---   function get_installed_fonts() ---   end ---end ----- </FIXME> - -local get_installed_fonts = nil -  --[[doc--  Auxiliary functions  --doc]]-- @@ -121,10 +98,46 @@ local sanitize_string = function (str)      return nil  end +--[[doc-- +This is a sketch of the db: + +    type dbobj = { +        mappings : fontentry list; +        status   : filestatus; +        version  : float; +    } +    and fontentry = { +        familyname  : string; +        filename    : (string * bool); +        fontname    : string; +        fullname    : string; +        names       : { +            family     : string; +            fullname   : string; +            psname     : string; +            subfamily  : string; +        } +        size        : int list; +        slant       : int; +        weight      : int; +        width       : int; +    } +    and filestatus = (fullname, { index : int list; timestamp : int }) dict + +beware that this is a reconstruction and may be incomplete. + +--doc]]-- +  local fontnames_init = function ( )      return {          mappings  = { },          status    = { }, +        --- adding filename mapping increases the +        --- size of the serialized db on my system +        --- (5840 font files) by a factor of ... +        barenames = { },--- incr. by 1.11 +        basenames = { },--- incr. by 1.22 +--      fullnames = { },--- incr. by 1.48          version   = names.version,      }  end @@ -180,12 +193,16 @@ local save_names  local scan_external_dir  local update_names +--- unit -> dbobj  load_names = function ( ) +    local starttime = os.gettimeofday()      local foundname, data = load_lua_file(names.path.path)      if data then          report("info", 1, "db",              "Font names database loaded", "%s", foundname) +        report("info", 1, "db", "Loading took %0.f ms", +                                1000*(os.gettimeofday()-starttime))      else          report("info", 0, "db",              [[Font names database not found, generating new one. @@ -204,10 +221,6 @@ do          regular    = { "normal",        "roman",                         "plain",         "book",                         "medium", }, -        --- TODO note from Élie Roux -        --- boldregular was for old versions of Linux Libertine, is it still useful? -        --- semibold is in new versions of Linux Libertine, but there is also a bold, -        --- not sure it's useful here...          bold       = { "demi",           "demibold",                         "semibold",       "boldregular",},          italic     = { "regularitalic",  "normalitalic", @@ -227,6 +240,55 @@ end  local fonts_loaded   = false  local fonts_reloaded = false +local crude_file_lookup_verbose = function (data, filename) +    local found = data.barenames[filename] +    if found then +        report("info", 0, "db", +            "crude file lookup: req=%s; hit=bare; ret=%s", +            filename, found[1]) +        return found +    end +--  found = data.fullnames[filename] +--  if found then +--      report("info", 0, "db", +--          "crude file lookup: req=%s; hit=bare; ret=%s", +--          filename, found[1]) +--      return found +--  end +    found = data.basenames[filename] +    if found then +        report("info", 0, "db", +            "crude file lookup: req=%s; hit=bare; ret=%s", +            filename, found[1]) +        return found +    end +    found = resolvers.findfile(filename, "tfm") +    if found then +        report("info", 0, "db", +            "crude file lookup: req=tfm; hit=bare; ret=%s", found) +        return { found, false } +    end +    found = resolvers.findfile(filename, "ofm") +    if found then +        report("info", 0, "db", +            "crude file lookup: req=ofm; hit=bare; ret=%s", found) +        return { found, false } +    end +    return false +end + +local crude_file_lookup = function (data, filename) +    local found = data.barenames[filename] +--             or data.fullnames[filename] +               or data.basenames[filename] +    if found then return found end +    found = resolvers.findfile(filename, "tfm") +    if found then return { found, false } end +    found = resolvers.findfile(filename, "ofm") +    if found then return { found, false } end +    return false +end +  --[[doc--  Luatex-fonts, the font-loader package luaotfload imports, comes with @@ -253,8 +315,10 @@ font database created by the mkluatexfontdb script.  ---   · specification: string (== <lookup> ":" <name>)  ---   · sub:      string  --- ---- the return value of “resolve” is the file name of the requested ---- font +--- the first return value of “resolve” is the file name of the +--- requested font (string) +--- the second is of type bool or string and indicates the subfont of a +--- ttc  ---  --- 'a -> 'a -> table -> (string * string | bool * bool)  --- @@ -264,6 +328,18 @@ font database created by the mkluatexfontdb script.  ---  ---   resolve = function (_,_,specification) -- the 1st two parameters are used by ConTeXt +    if not fonts_loaded then +        names.data   = load_names() +        fonts_loaded = true +    end +    local data = names.data + +    if specification.lookup == "file" then +        local found = crude_file_lookup(data, specification.name) +        --local found = crude_file_lookup_verbose(data, specification.name) +        if found then return found[1], found[2], true end +    end +      local name  = sanitize_string(specification.name)      local style = sanitize_string(specification.style) or "regular" @@ -274,152 +350,162 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con          size = specification.size / 65536      end -    if not fonts_loaded then -        names.data   = load_names() -        fonts_loaded = true +    if type(data) ~= "table" then +         --- this catches a case where load_names() doesn’t +         --- return a database object, which can happen only +         --- in case there is valid Lua code in the database, +         --- but it’s not a table, e.g. it contains an integer. +        if not fonts_reloaded then +            return reload_db("invalid database; not a table", +                             resolve, nil, nil, specification +                   ) +        end +        --- unsucessfully reloaded; bail +        return specification.name, false, false      end -    local data = names.data -    if type(data) == "table" then -        local db_version, nms_version = data.version, names.version -        if data.version ~= names.version then -            report("log", 0, "db", -                [[version mismatch; expected %4.3f, got %4.3f]], -                nms_version, db_version -            ) -            return reload_db(resolve, nil, nil, specification) +    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 +        ) +        return reload_db("version mismatch", resolve, nil, nil, specification) +    end + +    if not data.mappings then +        return reload_db("invalid database; missing font mapping", +                         resolve, nil, nil, specification +               ) +    end + +    local found = { } +    local synonym_set = style_synonyms.set +    for _,face in next, data.mappings do +        --- TODO we really should store those in dedicated +        --- .sanitized field +        local family    = sanitize_string(face.names and face.names.family) +        local subfamily = sanitize_string(face.names and face.names.subfamily) +        local fullname  = sanitize_string(face.names and face.names.fullname) +        local psname    = sanitize_string(face.names and face.names.psname) +        local fontname  = sanitize_string(face.fontname) +        local pfullname = 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 data.mappings then -            local found = { } -            local synonym_set = style_synonyms.set -            for _,face in next, data.mappings do -                --- TODO we really should store those in dedicated -                --- .sanitized field -                local family    = sanitize_string(face.names and face.names.family) -                local subfamily = sanitize_string(face.names and face.names.subfamily) -                local fullname  = sanitize_string(face.names and face.names.fullname) -                local psname    = sanitize_string(face.names and face.names.psname) -                local fontname  = sanitize_string(face.fontname) -                local pfullname = 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 then -                    if subfamily == style then -                        if optsize then -                            if dsnsize == size -                            or (size > minsize and size <= maxsize) then -                                found[1] = face -                                break -                            else -                                found[#found+1] = face -                            end -                        else -                            found[1] = face -                            break -                        end -                    elseif synonym_set[style] and -                           synonym_set[style][subfamily] then -                        if optsize then -                            if dsnsize == size -                            or (size > minsize and size <= maxsize) then -                                found[1] = face -                                break -                            else -                                found[#found+1] = face -                            end -                        else -                            found[1] = face -                            break -                        end -                    elseif subfamily == "regular" or -                           synonym_set.regular[subfamily] then -                        found.fallback = face +        if name == family then +            if subfamily == style then +                if optsize then +                    if dsnsize == size +                    or (size > minsize and size <= maxsize) then +                        found[1] = face +                        break +                    else +                        found[#found+1] = face                      end                  else -                    if name == fullname -                    or name == pfullname -                    or name == fontname -                    or name == psname then -                        if optsize then -                            if dsnsize == size -                            or (size > minsize and size <= maxsize) then -                                found[1] = face -                                break -                            else -                                found[#found+1] = face -                            end -                        else -                            found[1] = face -                            break -                        end +                    found[1] = face +                    break +                end +            elseif synonym_set[style] and +                    synonym_set[style][subfamily] then +                if optsize then +                    if dsnsize == size +                    or (size > minsize and size <= maxsize) then +                        found[1] = face +                        break +                    else +                        found[#found+1] = face                      end +                else +                    found[1] = face +                    break                  end +            elseif subfamily == "regular" or +                    synonym_set.regular[subfamily] then +                found.fallback = face              end -            if #found == 1 then -                if kpselookup(found[1].filename[1]) then -                    report("log", 0, "resolve", -                        "font family='%s', subfamily='%s' found: %s", -                        name, style, found[1].filename[1] -                    ) -                    return found[1].filename[1], found[1].filename[2], true -                end -            elseif #found > 1 then -                -- we found matching font(s) but not in the requested optical -                -- sizes, so we loop through the matches to find the one with -                -- least difference from the requested size. -                local closest -                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) -                    if difference < least then -                        closest = face -                        least   = difference +        else +            if name == fullname +            or name == pfullname +            or name == fontname +            or name == psname then +                if optsize then +                    if dsnsize == size +                    or (size > minsize and size <= maxsize) then +                        found[1] = face +                        break +                    else +                        found[#found+1] = face                      end +                else +                    found[1] = face +                    break                  end -                if kpselookup(closest.filename[1]) then -                    report("log", 0, "resolve", -                        "font family='%s', subfamily='%s' found: %s", -                        name, style, closest.filename[1] -                    ) -                    return closest.filename[1], closest.filename[2], true -                end -            elseif found.fallback then -                return found.fallback.filename[1], found.fallback.filename[2], true              end -            --- no font found so far -            if not fonts_reloaded then -                --- last straw: try reloading the database -                return reload_db(resolve, nil, nil, specification) -            else -                --- else, fallback to requested name -                --- specification.name is empty with absolute paths, looks -                --- like a bug in the specification parser <TODO< is it still -                --- relevant? looks not... -                return specification.name, false, false +        end +    end + +    if #found == 1 then +        if kpselookup(found[1].filename[1]) then +            report("log", 0, "resolve", +                "font family='%s', subfamily='%s' found: %s", +                name, style, found[1].filename[1] +            ) +            return found[1].filename[1], found[1].filename[2], true +        end +    elseif #found > 1 then +        -- we found matching font(s) but not in the requested optical +        -- sizes, so we loop through the matches to find the one with +        -- least difference from the requested size. +        local closest +        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) +            if difference < least then +                closest = face +                least   = difference              end          end -    else --- no db or outdated; reload names and retry -        if not fonts_reloaded then -            return reload_db(resolve, nil, nil, specification) -        else --- unsucessfully reloaded; bail -            return specification.name, false, false +        if kpselookup(closest.filename[1]) then +            report("log", 0, "resolve", +                "font family='%s', subfamily='%s' found: %s", +                name, style, closest.filename[1] +            ) +            return closest.filename[1], closest.filename[2], true          end +    elseif found.fallback then +        return found.fallback.filename[1], found.fallback.filename[2], true +    end + +    --- no font found so far +    if not fonts_reloaded then +        --- last straw: try reloading the database +        return reload_db( +            "unresoled font name: “" .. name .. "”", +            resolve, nil, nil, specification +        )      end + +    --- else, fallback to requested name +    --- specification.name is empty with absolute paths, looks +    --- like a bug in the specification parser <TODO< is it still +    --- relevant? looks not... +    return specification.name, false, false  end --- resolve()  --- when reload is triggered we update the database  --- and then re-run the caller with the arg list ---- ('a -> 'a) -> 'a list -> 'a -reload_db = function (caller, ...) -    report("log", 1, "db", "reload initiated") +--- string -> ('a -> 'a) -> 'a list -> 'a +reload_db = function (why, caller, ...) +    report("log", 1, "db", "reload initiated; reason: “%s”", why)      names.data = update_names()      save_names(names.data)      fonts_reloaded = true @@ -466,67 +552,65 @@ find_closest = function (name, limit)      local data = names.data -    if type(data) == "table" then -        local by_distance   = { } --- (int, string list) dict -        local distances     = { } --- int list -        local cached        = { } --- (string, int) dict -        local mappings      = data.mappings -        local n_fonts       = #mappings - -        for n = 1, n_fonts do -            local current    = mappings[n] -            local cnames     = current.names -            --[[ -                This is simplistic but surpisingly fast. -                Matching is performed against the “family” name -                of a db record. We then store its “fullname” at -                it edit distance. -                We should probably do some weighting over all the -                font name categories as well as whatever agrep -                does. -            --]] -            if cnames then -                local fullname, family = cnames.fullname, cnames.family -                family = sanitize_string(family) - -                local dist = cached[family]--- maybe already calculated -                if not dist then -                    dist = iterative_levenshtein(name, family) -                    cached[family] = dist -                end -                local namelst = by_distance[dist] -                if not namelst then --- first entry -                    namelst = { fullname } -                    distances[#distances+1] = dist -                else --- append -                    namelst[#namelst+1] = fullname -                end -                by_distance[dist] = namelst +    if type(data) ~= "table" then +        return reload_db("no database", find_closest, name) +    end +    local by_distance   = { } --- (int, string list) dict +    local distances     = { } --- int list +    local cached        = { } --- (string, int) dict +    local mappings      = data.mappings +    local n_fonts       = #mappings + +    for n = 1, n_fonts do +        local current    = mappings[n] +        local cnames     = current.names +        --[[ +            This is simplistic but surpisingly fast. +            Matching is performed against the “family” name +            of a db record. We then store its “fullname” at +            it edit distance. +            We should probably do some weighting over all the +            font name categories as well as whatever agrep +            does. +        --]] +        if cnames then +            local fullname, family = cnames.fullname, cnames.family +            family = sanitize_string(family) + +            local dist = cached[family]--- maybe already calculated +            if not dist then +                dist = iterative_levenshtein(name, family) +                cached[family] = dist              end -        end - -        --- print the matches according to their distance -        local n_distances = #distances -        if n_distances > 0 then --- got some data -            tablesort(distances) -            limit = mathmin(n_distances, limit) -            report(false, 1, "query", -                    "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 -                 .. "\n    " .. tableconcat(namelst, "\n    ") -                ) +            local namelst = by_distance[dist] +            if not namelst then --- first entry +                namelst = { fullname } +                distances[#distances+1] = dist +            else --- append +                namelst[#namelst+1] = fullname              end +            by_distance[dist] = namelst +        end +    end -            return true +    --- print the matches according to their distance +    local n_distances = #distances +    if n_distances > 0 then --- got some data +        tablesort(distances) +        limit = mathmin(n_distances, limit) +        report(false, 1, "query", +                "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 +                .. "\n    " .. tableconcat(namelst, "\n    ") +            )          end -        return false -    else --- need reload -        return reload_db(find_closest, name) + +        return true      end      return false  end --- find_closest() @@ -595,107 +679,173 @@ font_fullinfo = function (filename, subfont, texmf)      return tfmdata  end -local load_font = function (filename, fontnames, newfontnames, texmf) -    local newmappings = newfontnames.mappings -    local newstatus   = newfontnames.status -    local mappings    = fontnames.mappings -    local status      = fontnames.status -    local basename    = filebasename(filename) -    local basefile    = texmf and basename or filename -    if filename then -        if names.blacklist[filename] or -           names.blacklist[basename] then -            report("log", 2, "db", "ignoring font", "%s", filename) -            return -        end -        local timestamp, db_timestamp -        db_timestamp        = status[basefile] and status[basefile].timestamp -        timestamp           = lfs.attributes(filename, "modification") - -        local index_status = newstatus[basefile] or (not texmf and newstatus[basename]) -        if index_status and index_status.timestamp == timestamp then -            -- already indexed this run -            return -        end +--- we return true if the fond is new or re-indexed +--- string -> dbobj -> dbobj -> bool -> bool +local load_font = function (fullname, fontnames, newfontnames, texmf) +    local newmappings   = newfontnames.mappings +    local newstatus     = newfontnames.status -        newstatus[basefile] = newstatus[basefile] or { } -        newstatus[basefile].timestamp = timestamp -        newstatus[basefile].index     = newstatus[basefile].index or { } +--  local newfullnames  = newfontnames.fullnames +    local newbasenames  = newfontnames.basenames +    local newbarenames  = newfontnames.barenames -        if db_timestamp == timestamp and not newstatus[basefile].index[1] then -            for _,v in next, status[basefile].index do -                local index = #newstatus[basefile].index -                newmappings[#newmappings+1]        = mappings[v] -                newstatus[basefile].index[index+1] = #newmappings -            end -            report("log", 1, "db", "font already indexed", "%s", basefile) -            return +    local mappings      = fontnames.mappings +    local status        = fontnames.status +--  local fullnames     = fontnames.fullnames +    local basenames     = fontnames.basenames +    local barenames     = fontnames.barenames + +    local basename      = filebasename(fullname) +    local barename      = filenameonly(fullname) + +    --- entryname is apparently the identifier a font is +    --- loaded by; it is different for files in the texmf +    --- (due to kpse? idk.) +    --- entryname = texmf : true -> basename | false -> fullname +    local entryname     = texmf and basename or fullname + +    if not fullname then return false end + +    if names.blacklist[fullname] +    or names.blacklist[basename] +    then +        report("log", 2, "db", +            "ignoring blacklisted font “%s”", fullname) +        return false +    end +    local timestamp, db_timestamp +    db_timestamp        = status[entryname] +                        and status[entryname].timestamp +    timestamp           = lfs.attributes(fullname, "modification") + +    local index_status = newstatus[entryname] +                        or (not texmf and newstatus[basename]) +    local teststat = newstatus[entryname] +    --- index_status: nil | false | table +    if index_status and index_status.timestamp == timestamp then +        -- already indexed this run +        return false +    end + +    newstatus[entryname]           = newstatus[entryname] or { } +    newstatus[entryname].timestamp = timestamp +    newstatus[entryname].index     = newstatus[entryname].index or { } + +    if  db_timestamp == timestamp +    and not newstatus[entryname].index[1] then +        for _,v in next, status[entryname].index do +            local index    = #newstatus[entryname].index +            local fullinfo = mappings[v] +            newmappings[#newmappings+1]         = fullinfo --- keep +            newstatus[entryname].index[index+1] = #newmappings +--          newfullnames[fullname] = fullinfo.filename +            newbasenames[basename] = fullinfo.filename +            newbarenames[barename] = fullinfo.filename          end -        local info = fontloader.info(filename) -        if info then -            if type(info) == "table" and #info > 1 then -                for i in next, info do -                    local fullinfo = font_fullinfo(filename, i-1, texmf) -                    if not fullinfo then -                        return -                    end -                    local index = newstatus[basefile].index[i] -                    if newstatus[basefile].index[i] then -                        index = newstatus[basefile].index[i] -                    else -                        index = #newmappings+1 -                    end -                    newmappings[index]           = fullinfo -                    newstatus[basefile].index[i] = index -                end -            else -                local fullinfo = font_fullinfo(filename, false, texmf) +        report("log", 2, "db", "font “%s” already indexed", entryname) +        return false +    end + +    local info = fontloaderinfo(fullname) +    if info then +        if type(info) == "table" and #info > 1 then --- ttc +            for i in next, info do +                local fullinfo = font_fullinfo(fullname, i-1, texmf)                  if not fullinfo then -                    return +                    return false                  end -                local index -                if newstatus[basefile].index[1] then -                    index = newstatus[basefile].index[1] +                local index = newstatus[entryname].index[i] +                if newstatus[entryname].index[i] then +                    index = newstatus[entryname].index[i]                  else                      index = #newmappings+1                  end -                newmappings[index]           = fullinfo -                newstatus[basefile].index[1] = index +                newmappings[index]            = fullinfo +--              newfullnames[fullname]        = fullinfo.filename +                newbasenames[basename]        = fullinfo.filename +                newbarenames[barename]        = fullinfo.filename +                newstatus[entryname].index[i] = index              end          else -            report("log", 1, "db", "failed to load", "%s", basefile) +            local fullinfo = font_fullinfo(fullname, false, texmf) +            if not fullinfo then +                return false +            end +            local index +            if newstatus[entryname].index[1] then +                index = newstatus[entryname].index[1] +            else +                index = #newmappings+1 +            end +            newmappings[index]            = fullinfo +--          newfullnames[fullname]        = { fullinfo.filename[1], fullinfo.filename[2] } +            newbasenames[basename]        = { fullinfo.filename[1], fullinfo.filename[2] } +            newbarenames[barename]        = { fullinfo.filename[1], fullinfo.filename[2] } +            newstatus[entryname].index[1] = index          end + +    else --- missing info +        report("log", 1, "db", "failed to load “%s”", entryname) +        return false      end +    return true  end -local function path_normalize(path) -    --[[ -        path normalization: -        - a\b\c  -> a/b/c -        - a/../b -> b -        - /cygdrive/a/b -> a:/b -        - reading symlinks under non-Win32 -        - using kpse.readable_file on Win32 -    ]] -    if os.type == "windows" or os.type == "msdos" or os.name == "cygwin" then -        path = stringgsub(path, '\\', '/') -        path = stringlower(path) -        path = stringgsub(path, '^/cygdrive/(%a)/', '%1:/') -    end -    if os.type ~= "windows" and os.type ~= "msdos" then -        local dest = lfs.readlink(path) -        if dest then -            if kpsereadable_file(dest) then -                path = dest -            elseif kpsereadable_file(filejoin(file.dirname(path), dest)) then -                path = filejoin(file.dirname(path), dest) -            else -                -- broken symlink? +local path_normalize +do +    --- os.type and os.name are constants so we +    --- choose a normalization function in advance +    --- instead of testing with every call +    local os_type, os_name = os.type, os.name +    local filecollapsepath = filecollapsepath +    local lfsreadlink      = lfs.readlink + +    --- windows and dos +    if os_type == "windows" or os_type == "msdos" then +        --- ms platfom specific stuff +        path_normalize = function (path) +            path = stringgsub(path, '\\', '/') +            path = stringlower(path) +            path = stringgsub(path, '^/cygdrive/(%a)/', '%1:/') +            path = filecollapsepath(path) +            return path +        end + +    elseif os_name == "cygwin" then -- union of ms + unix +        path_normalize = function (path) +            path = stringgsub(path, '\\', '/') +            path = stringlower(path) +            path = stringgsub(path, '^/cygdrive/(%a)/', '%1:/') +            local dest = lfsreadlink(path) +            if dest then +                if kpsereadable_file(dest) then +                    path = dest +                elseif kpsereadable_file(filejoin(filedirname(path), dest)) then +                    path = filejoin(file.dirname(path), dest) +                else +                    -- broken symlink? +                end              end +            path = filecollapsepath(path) +            return path +        end + +    else -- posix +        path_normalize = function (path) +            local dest = lfsreadlink(path) +            if dest then +                if kpsereadable_file(dest) then +                    path = dest +                elseif kpsereadable_file(filejoin(filedirname(path), dest)) then +                    path = filejoin(file.dirname(path), dest) +                else +                    -- broken symlink? +                end +            end +            path = filecollapsepath(path) +            return path          end      end -    path = filecollapsepath(path) -    return path  end  fonts.path_normalize = path_normalize @@ -723,7 +873,7 @@ local function read_blacklist()                      if stringsub(line, 1, 1) == "-" then                          whitelist[stringsub(line, 2, -1)] = true                      else -                        report("log", 2, "db", "blacklisted file", "%s", line) +                        report("log", 2, "db", "blacklisted file “%s”", line)                          blacklist[line] = true                      end                  end @@ -741,69 +891,40 @@ for key, value in next, font_extensions do     font_extensions_set[value] = true  end ---local installed_fonts_scanned = false --- ugh - ---- we already have scan_os_fonts don’t we? - ---local function scan_installed_fonts(fontnames, newfontnames) ---    --- Try to query and add font list from operating system. ---    --- This uses the lualatex-platform module. ---    --- <phg>what for? why can’t we do this in Lua?</phg> ---    report("info", 0, "Scanning fonts known to operating system...") ---    local fonts = get_installed_fonts() ---    if fonts and #fonts > 0 then ---        installed_fonts_scanned = true ---        report("log", 2, "operating system fonts found", "%d", #fonts) ---        for key, value in next, fonts do ---            local file = value.path ---            if file then ---                local ext = fileextname(file) ---                if ext and font_extensions_set[ext] then ---                file = path_normalize(file) ---                    report("log", 1, "loading font", "%s", file) ---                load_font(file, fontnames, newfontnames, false) ---                end ---            end ---        end ---    else ---        report("log", 2, "Could not retrieve list of installed fonts") ---    end ---end - -local function scan_dir(dirname, fontnames, newfontnames, texmf) +--- string -> dbobj -> dbobj -> bool -> (int * int) +local scan_dir = function (dirname, fontnames, newfontnames, texmf)      --[[      This function scans a directory and populates the list of fonts      with all the fonts it finds.      - dirname is the name of the directory to scan -    - names is the font database to fill +    - names is the font database to fill -> no such term!!!      - texmf is a boolean saying if we are scanning a texmf directory      ]] -    local list, found = { }, { } -    local nbfound = 0 +    local n_scanned, n_new = 0, 0   --- total of fonts collected      report("log", 2, "db", "scanning", "%s", dirname)      for _,i in next, font_extensions do          for _,ext in next, { i, stringupper(i) } do -            found = dirglob(stringformat("%s/**.%s$", dirname, ext)) -            -- note that glob fails silently on broken symlinks, which happens -            -- sometimes in TeX Live. -            report("log", 2, "db", -                "fonts found", "%s '%s' fonts found", #found, ext) -            nbfound = nbfound + #found -            tableappend(list, found) +            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("log", 2, "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) +                report("log", 2, "db", "loading font “%s”", fullname) +                local new = load_font(fullname, fontnames, newfontnames, texmf) +                if new then n_new = n_new + 1 end +            end          end      end -    report("log", 2, "db", -        "fonts found", "%d fonts found in '%s'", nbfound, dirname) - -    for _,file in next, list do -        file = path_normalize(file) -        report("log", 1, "db", -            "loading font", "%s", file) -        load_font(file, fontnames, newfontnames, texmf) -    end +    report("log", 2, "db", "%d fonts found in '%s'", n_scanned, dirname) +    return n_scanned, n_new  end  local function scan_texmf_fonts(fontnames, newfontnames) +    local n_scanned, n_new = 0, 0      --[[      This function scans all fonts in the texmf tree, through kpathsea      variables OPENTYPEFONTS and TTFONTS of texmf.cnf @@ -817,9 +938,12 @@ local function scan_texmf_fonts(fontnames, newfontnames)      fontdirs       = fontdirs .. stringgsub(kpseexpand_path("$TTFONTS"), "^%.", "")      if not stringis_empty(fontdirs) then          for _,d in next, filesplitpath(fontdirs) do -            scan_dir(d, fontnames, newfontnames, true) +            local found, new = scan_dir(d, fontnames, newfontnames, true) +            n_scanned = n_scanned + found +            n_new     = n_new     + new          end      end +    return n_scanned, n_new  end  --[[ @@ -851,7 +975,7 @@ read_fonts_conf = function (path, results, passed_paths)      passed_paths[#passed_paths+1] = path      passed_paths_set = tabletohash(passed_paths, true)      if not fh then -        report("log", 2, "db", "cannot open file", "%s", path) +        report("log", 2, "db", "cannot open file %s", path)          return results      end      local incomments = false @@ -942,8 +1066,6 @@ local function get_os_dirs()      else           local passed_paths = {}          local os_dirs = {} -        -- what about ~/config/fontconfig/fonts.conf etc?  -        -- Answer: they should be included by the others, please report if it's not          for _,p in next, {"/usr/local/etc/fonts/fonts.conf", "/etc/fonts/fonts.conf"} do              if lfs.isfile(p) then                  read_fonts_conf(p, os_dirs, passed_paths) @@ -955,6 +1077,7 @@ local function get_os_dirs()  end  local function scan_os_fonts(fontnames, newfontnames) +    local n_scanned, n_new = 0, 0      --[[      This function scans the OS fonts through        - fontcache for Unix (reads the fonts.conf file and scans the directories) @@ -963,17 +1086,24 @@ local function scan_os_fonts(fontnames, newfontnames)      report("info", 1, "db", "Scanning OS fonts...")      report("info", 2, "db", "Searching in static system directories...")      for _,d in next, get_os_dirs() do -        scan_dir(d, fontnames, newfontnames, false) +        local found, new = scan_dir(d, fontnames, newfontnames, false) +        n_scanned = n_scanned + found +        n_new     = n_new     + new      end +    return n_scanned, n_new  end +--- dbobj -> bool -> dbobj  update_names = function (fontnames, force) +    local starttime = os.gettimeofday() +    local n_scanned, n_new = 0, 0      --[[      The main function, scans everything -    - fontnames is the final table to return +    - “newfontnames” is the final table to return      - force is whether we rebuild it from scratch or not      ]] -    report("info", 1, "db", "Updating the font names database") +    report("info", 1, "db", "Updating the font names database" +                         .. (force and " forcefully" or ""))      if force then          fontnames = fontnames_init() @@ -991,16 +1121,29 @@ update_names = function (fontnames, force)      read_blacklist()      --installed_fonts_scanned = false      --scan_installed_fonts(fontnames, newfontnames) --- see fixme above -    scan_texmf_fonts(fontnames, newfontnames) +    local scanned, new = scan_texmf_fonts(fontnames, newfontnames) +    n_scanned = n_scanned + scanned +    n_new     = n_new     + new      --if  not installed_fonts_scanned      --and stringis_empty(kpseexpand_path("$OSFONTDIR"))      if stringis_empty(kpseexpand_path("$OSFONTDIR"))      then -        scan_os_fonts(fontnames, newfontnames) +        local scanned, new = scan_os_fonts(fontnames, newfontnames) +        n_scanned = n_scanned + scanned +        n_new     = n_new     + new      end +    --- stats: +    ---            before rewrite   | after rewrite +    ---   partial:         804 ms   |   701 ms +    ---   forced:        45384 ms   | 44714 ms +    report("info", 1, "db", +           "Scanned %d font files; %d new entries.", n_scanned, n_new) +    report("info", 1, "db", +           "Rebuilt in %0.f ms", 1000*(os.gettimeofday()-starttime))      return newfontnames  end +--- dbobj -> unit  save_names = function (fontnames)      local path  = names.path.dir      if not lfs.isdir(path) then @@ -1028,8 +1171,9 @@ scan_external_dir = function (dir)          fonts_loaded    = true      end      new_names = tablecopy(old_names) -    scan_dir(dir, old_names, new_names) +    local n_scanned, n_new = scan_dir(dir, old_names, new_names)      names.data = new_names +    return n_scanned, n_new  end  --- export functionality to the namespace “fonts.names” diff --git a/luaotfload-features.lua b/luaotfload-features.lua index 0121ede..78f8256 100644 --- a/luaotfload-features.lua +++ b/luaotfload-features.lua @@ -37,6 +37,12 @@ local stringfind       = string.find  local stringexplode    = string.explode  local stringis_empty   = string.is_empty +--[[doc-- +Apparently, these “modifiers” are another measure of emulating \XETEX, +cf. “About \XETEX”, by Jonathan Kew, 2005; and +    “The \XETEX Reference Guide”, by Will Robertson, 2011. +--doc]]-- +  local supported = {      b    = "bold",      i    = "italic", @@ -105,6 +111,8 @@ local defaults = {      },  } +local global_defaults = { mode = "node" } +  defaults.beng = defaults.deva  defaults.guru = defaults.deva  defaults.gujr = defaults.deva @@ -123,22 +131,36 @@ defaults.tibt = defaults.khmr  defaults.lao  = defaults.thai -local function set_default_features(script) -    local features -    local script = script or "dflt" +--- (string, string) dict -> (string, string) dict +local set_default_features = function (speclist) +    local script = speclist.script or "dflt" +      report("log", 0, "load font",          "auto-selecting default features for script: %s",          script) -    if defaults[script] then -        features = defaults[script] -    else -        features = defaults["dflt"] + +    local requested = defaults[script] +    if not requested then +        report("log", 0, "load font", +            "no defaults for script “%s”, falling back to “dflt”", +            script) +        requested = defaults.dflt      end -    for _,v in next, features do -        if feature_list[v] ~= false then -            feature_list[v] = true + +    for i=1, #requested do +        local feat = requested[i] +        if speclist[feat] ~= false then +            speclist[feat] = true          end      end + +    for feat, state in next, global_defaults do +        --- This is primarily intended for setting node +        --- mode unless “base” is requested, as stated +        --- in the manual. +        if not speclist[feat] then speclist[feat] = state end +    end +    return speclist  end  local function issome ()    feature_list.lookup = 'name' end @@ -173,7 +195,7 @@ local pattern    = (filename + fontname) * subvalue^0 * stylespec^0 * options^0  local function colonized(specification) -- xetex mode      feature_list = { }      lpeg.match(pattern,specification.specification) -    set_default_features(feature_list.script) +    feature_list = set_default_features(feature_list)      if feature_list.style then          specification.style = feature_list.style          feature_list.style = nil @@ -213,20 +235,22 @@ end  fonts.definers.registersplit(":",colonized,"cryptic")  fonts.definers.registersplit("", colonized,"more cryptic") -- catches \font\text=[names] -function fonts.definers.applypostprocessors(tfmdata) -    local postprocessors = tfmdata.postprocessors -    if postprocessors then -        for i=1,#postprocessors do -            local extrahash = postprocessors[i](tfmdata) -- after scaling etc -            if type(extrahash) == "string" and extrahash ~= "" then -                -- e.g. a reencoding needs this -                extrahash = string.gsub(lower(extrahash),"[^a-z]","-") -                tfmdata.properties.fullname = format("%s-%s",tfmdata.properties.fullname,extrahash) -            end -        end -    end -    return tfmdata -end +--- TODO below section is literally the same in luatex-fonts-def +---      why is it here? +--function fonts.definers.applypostprocessors(tfmdata) +--    local postprocessors = tfmdata.postprocessors +--    if postprocessors then +--        for i=1,#postprocessors do +--            local extrahash = postprocessors[i](tfmdata) -- after scaling etc +--            if type(extrahash) == "string" and extrahash ~= "" then +--                -- e.g. a reencoding needs this +--                extrahash = string.gsub(lower(extrahash),"[^a-z]","-") +--                tfmdata.properties.fullname = format("%s-%s",tfmdata.properties.fullname,extrahash) +--            end +--        end +--    end +--    return tfmdata +--end  ---[[ end included font-ltx.lua ]]  --[[doc-- diff --git a/luaotfload-merged.lua b/luaotfload-merged.lua index 314305a..da5e35a 100644 --- a/luaotfload-merged.lua +++ b/luaotfload-merged.lua @@ -1,6 +1,6 @@  -- merged file : luatex-fonts-merged.lua  -- parent file : luatex-fonts.lua --- merge date  : 04/20/13 13:33:53 +-- merge date  : 04/24/13 13:39:43  do -- begin closure to overcome local limits and interference @@ -2978,7 +2978,9 @@ function resolvers.unresolve(s)    return s  end  caches={} -local writable,readables=nil,{} +local writable=nil +local readables={} +local usingjit=jit  if not caches.namespace or caches.namespace=="" or caches.namespace=="context" then    caches.namespace='generic'  end @@ -3038,7 +3040,7 @@ end  local function makefullname(path,name)    if path and path~="" then      name="temp-"..name  -    return file.addsuffix(file.join(path,name),"lua"),file.addsuffix(file.join(path,name),"luc") +    return file.addsuffix(file.join(path,name),"lua"),file.addsuffix(file.join(path,name),usingjit and "lub" or "luc")    end  end  function caches.is_writable(path,name) @@ -3085,26 +3087,19 @@ function caches.savedata(path,name,data)      end    end  end -caches.compilemethod="both"  function caches.compile(data,luaname,lucname) -  local done=false -  if caches.compilemethod=="luac" or caches.compilemethod=="both" then -    done=os.spawn("texluac -o "..string.quoted(lucname).." -s "..string.quoted(luaname))==0 +  local d=io.loaddata(luaname) +  if not d or d=="" then +    d=table.serialize(data,true)     end -  if not done and (caches.compilemethod=="dump" or caches.compilemethod=="both") then -    local d=io.loaddata(luaname) -    if not d or d=="" then -      d=table.serialize(data,true)  -    end -    if d and d~="" then -      local f=io.open(lucname,'w') -      if f then -        local s=loadstring(d) -        if s then -          f:write(string.dump(s,true)) -        end -        f:close() +  if d and d~="" then +    local f=io.open(lucname,'w') +    if f then +      local s=loadstring(d) +      if s then +        f:write(string.dump(s,true))        end +      f:close()      end    end  end @@ -6103,6 +6098,14 @@ actions["reorganize lookups"]=function(data,filename,raw)                local current=coverage.current                if current then                  current=t_uncover(splitter,t_u_cache,current) +                local lookups=rule.lookups +                if lookups then +                  for i=1,#current do +                    if not lookups[i] then +                      lookups[i]=""  +                    end +                  end +                end                  rule.current=t_hashed(current,t_h_cache)                end                local after=coverage.after @@ -10313,6 +10316,819 @@ end -- closure  do -- begin closure to overcome local limits and interference +if not modules then modules={} end modules ['font-otp']={ +  version=1.001, +  comment="companion to font-otf.lua (packing)", +  author="Hans Hagen, PRAGMA-ADE, Hasselt NL", +  copyright="PRAGMA ADE / ConTeXt Development Team", +  license="see context related readme files" +} +local next,type=next,type +local sort,concat=table.sort,table.concat +local sortedhash=table.sortedhash +local trace_packing=false trackers.register("otf.packing",function(v) trace_packing=v end) +local trace_loading=false trackers.register("otf.loading",function(v) trace_loading=v end) +local report_otf=logs.reporter("fonts","otf loading") +fonts=fonts or {} +local handlers=fonts.handlers or {} +fonts.handlers=handlers +local otf=handlers.otf or {} +handlers.otf=otf +local enhancers=otf.enhancers or {} +otf.enhancers=enhancers +local glists=otf.glists or { "gsub","gpos" } +otf.glists=glists +local criterium=1 +local threshold=0 +local function tabstr_normal(t) +  local s={} +  local n=0 +  for k,v in next,t do +    n=n+1 +    if type(v)=="table" then +      s[n]=k..">"..tabstr_normal(v) +    elseif v==true then +      s[n]=k.."+"  +    elseif v then +      s[n]=k.."="..v +    else +      s[n]=k.."-"  +    end +  end +  if n==0 then +    return "" +  elseif n==1 then +    return s[1] +  else +    sort(s)  +    return concat(s,",") +  end +end +local function tabstr_flat(t) +  local s={} +  local n=0 +  for k,v in next,t do +    n=n+1 +    s[n]=k.."="..v +  end +  if n==0 then +    return "" +  elseif n==1 then +    return s[1] +  else +    sort(s)  +    return concat(s,",") +  end +end +local function tabstr_mixed(t)  +  local s={} +  local n=#t +  if n==0 then +    return "" +  elseif n==1 then +    local k=t[1] +    if k==true then +      return "++"  +    elseif k==false then +      return "--"  +    else +      return tostring(k)  +    end +  else +    for i=1,n do +      local k=t[i] +      if k==true then +        s[i]="++"  +      elseif k==false then +        s[i]="--"  +      else +        s[i]=k  +      end +    end +    return concat(s,",") +  end +end +local function tabstr_boolean(t) +  local s={} +  local n=0 +  for k,v in next,t do +    n=n+1 +    if v then +      s[n]=k.."+" +    else +      s[n]=k.."-" +    end +  end +  if n==0 then +    return "" +  elseif n==1 then +    return s[1] +  else +    sort(s)  +    return concat(s,",") +  end +end +local function packdata(data) +  if data then +    local h,t,c={},{},{} +    local hh,tt,cc={},{},{} +    local nt,ntt=0,0 +    local function pack_normal(v) +      local tag=tabstr_normal(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_flat(v) +      local tag=tabstr_flat(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_boolean(v) +      local tag=tabstr_boolean(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_indexed(v) +      local tag=concat(v," ") +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_mixed(v) +      local tag=tabstr_mixed(v) +      local ht=h[tag] +      if ht then +        c[ht]=c[ht]+1 +        return ht +      else +        nt=nt+1 +        t[nt]=v +        h[tag]=nt +        c[nt]=1 +        return nt +      end +    end +    local function pack_final(v) +      if c[v]<=criterium then +        return t[v] +      else +        local hv=hh[v] +        if hv then +          return hv +        else +          ntt=ntt+1 +          tt[ntt]=t[v] +          hh[v]=ntt +          cc[ntt]=c[v] +          return ntt +        end +      end +    end +    local function success(stage,pass) +      if nt==0 then +        if trace_loading or trace_packing then +          report_otf("pack quality: nothing to pack") +        end +        return false +      elseif nt>=threshold then +        local one,two,rest=0,0,0 +        if pass==1 then +          for k,v in next,c do +            if v==1 then +              one=one+1 +            elseif v==2 then +              two=two+1 +            else +              rest=rest+1 +            end +          end +        else +          for k,v in next,cc do +            if v>20 then +              rest=rest+1 +            elseif v>10 then +              two=two+1 +            else +              one=one+1 +            end +          end +          data.tables=tt +        end +        if trace_loading or trace_packing then +          report_otf("pack quality: stage %s, pass %s, %s packed, 1-10:%s, 11-20:%s, rest:%s (criterium: %s)",stage,pass,one+two+rest,one,two,rest,criterium) +        end +        return true +      else +        if trace_loading or trace_packing then +          report_otf("pack quality: stage %s, pass %s, %s packed, aborting pack (threshold: %s)",stage,pass,nt,threshold) +        end +        return false +      end +    end +    local function packers(pass) +      if pass==1 then +        return pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed +      else +        return pack_final,pack_final,pack_final,pack_final,pack_final +      end +    end +    local resources=data.resources +    local lookuptypes=resources.lookuptypes +    for pass=1,2 do +      if trace_packing then +        report_otf("start packing: stage 1, pass %s",pass) +      end +      local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) +      for unicode,description in next,data.descriptions do +        local boundingbox=description.boundingbox +        if boundingbox then +          description.boundingbox=pack_indexed(boundingbox) +        end +        local slookups=description.slookups +        if slookups then +          for tag,slookup in next,slookups do +            local what=lookuptypes[tag] +            if what=="pair" then +              local t=slookup[2] if t then slookup[2]=pack_indexed(t) end +              local t=slookup[3] if t then slookup[3]=pack_indexed(t) end +            elseif what~="substitution" then +              slookups[tag]=pack_indexed(slookup)  +            end +          end +        end +        local mlookups=description.mlookups +        if mlookups then +          for tag,mlookup in next,mlookups do +            local what=lookuptypes[tag] +            if what=="pair" then +              for i=1,#mlookup do +                local lookup=mlookup[i] +                local t=lookup[2] if t then lookup[2]=pack_indexed(t) end +                local t=lookup[3] if t then lookup[3]=pack_indexed(t) end +              end +            elseif what~="substitution" then +              for i=1,#mlookup do +                mlookup[i]=pack_indexed(mlookup[i])  +              end +            end +          end +        end +        local kerns=description.kerns +        if kerns then +          for tag,kern in next,kerns do +            kerns[tag]=pack_flat(kern) +          end +        end +        local math=description.math +        if math then +          local kerns=math.kerns +          if kerns then +            for tag,kern in next,kerns do +              kerns[tag]=pack_normal(kern) +            end +          end +        end +        local anchors=description.anchors +        if anchors then +          for what,anchor in next,anchors do +            if what=="baselig" then +              for _,a in next,anchor do +                for k=1,#a do +                  a[k]=pack_indexed(a[k]) +                end +              end +            else +              for k,v in next,anchor do +                anchor[k]=pack_indexed(v) +              end +            end +          end +        end +      end +      local lookups=data.lookups +      if lookups then +        for _,lookup in next,lookups do +          local rules=lookup.rules +          if rules then +            for i=1,#rules do +              local rule=rules[i] +              local r=rule.before    if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end +              local r=rule.after    if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end +              local r=rule.current   if r then for i=1,#r do r[i]=pack_boolean(r[i]) end end +              local r=rule.replacements if r then rule.replacements=pack_flat  (r)  end  +              local r=rule.lookups   if r then rule.lookups=pack_indexed(r)  end +            end +          end +        end +      end +      local anchor_to_lookup=resources.anchor_to_lookup +      if anchor_to_lookup then +        for anchor,lookup in next,anchor_to_lookup do +          anchor_to_lookup[anchor]=pack_normal(lookup) +        end +      end +      local lookup_to_anchor=resources.lookup_to_anchor +      if lookup_to_anchor then +        for lookup,anchor in next,lookup_to_anchor do +          lookup_to_anchor[lookup]=pack_normal(anchor) +        end +      end +      local sequences=resources.sequences +      if sequences then +        for feature,sequence in next,sequences do +          local flags=sequence.flags +          if flags then +            sequence.flags=pack_normal(flags) +          end +          local subtables=sequence.subtables +          if subtables then +            sequence.subtables=pack_normal(subtables) +          end +          local features=sequence.features +          if features then +            for script,feature in next,features do +              features[script]=pack_normal(feature) +            end +          end +        end +      end +      local lookups=resources.lookups +      if lookups then +        for name,lookup in next,lookups do +          local flags=lookup.flags +          if flags then +            lookup.flags=pack_normal(flags) +          end +          local subtables=lookup.subtables +          if subtables then +            lookup.subtables=pack_normal(subtables) +          end +        end +      end +      local features=resources.features +      if features then +        for _,what in next,glists do +          local list=features[what] +          if list then +            for feature,spec in next,list do +              list[feature]=pack_normal(spec) +            end +          end +        end +      end +      if not success(1,pass) then +        return +      end +    end +    if nt>0 then +      for pass=1,2 do +        if trace_packing then +          report_otf("start packing: stage 2, pass %s",pass) +        end +        local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) +        for unicode,description in next,data.descriptions do +          local kerns=description.kerns +          if kerns then +            description.kerns=pack_normal(kerns) +          end +          local math=description.math +          if math then +            local kerns=math.kerns +            if kerns then +              math.kerns=pack_normal(kerns) +            end +          end +          local anchors=description.anchors +          if anchors then +            description.anchors=pack_normal(anchors) +          end +          local mlookups=description.mlookups +          if mlookups then +            for tag,mlookup in next,mlookups do +              mlookups[tag]=pack_normal(mlookup) +            end +          end +        end +        local lookups=data.lookups +        if lookups then +          for _,lookup in next,lookups do +            local rules=lookup.rules +            if rules then +              for i=1,#rules do  +                local rule=rules[i] +                local r=rule.before if r then rule.before=pack_normal(r) end +                local r=rule.after  if r then rule.after=pack_normal(r) end +                local r=rule.current if r then rule.current=pack_normal(r) end +              end +            end +          end +        end +        local sequences=resources.sequences +        if sequences then +          for feature,sequence in next,sequences do +            sequence.features=pack_normal(sequence.features) +          end +        end +        if not success(2,pass) then +        end +      end +      for pass=1,2 do +        local pack_normal,pack_indexed,pack_flat,pack_boolean,pack_mixed=packers(pass) +        for unicode,description in next,data.descriptions do +          local slookups=description.slookups +          if slookups then +            description.slookups=pack_normal(slookups) +          end +          local mlookups=description.mlookups +          if mlookups then +            description.mlookups=pack_normal(mlookups) +          end +        end +      end +    end +  end +end +local unpacked_mt={ +  __index=function(t,k) +      t[k]=false +      return k  +    end +} +local function unpackdata(data) +  if data then +    local tables=data.tables +    if tables then +      local resources=data.resources +      local lookuptypes=resources.lookuptypes +      local unpacked={} +      setmetatable(unpacked,unpacked_mt) +      for unicode,description in next,data.descriptions do +        local tv=tables[description.boundingbox] +        if tv then +          description.boundingbox=tv +        end +        local slookups=description.slookups +        if slookups then +          local tv=tables[slookups] +          if tv then +            description.slookups=tv +            slookups=unpacked[tv] +          end +          if slookups then +            for tag,lookup in next,slookups do +              local what=lookuptypes[tag] +              if what=="pair" then +                local tv=tables[lookup[2]] +                if tv then +                  lookup[2]=tv +                end +                local tv=tables[lookup[3]] +                if tv then +                  lookup[3]=tv +                end +              elseif what~="substitution" then +                local tv=tables[lookup] +                if tv then +                  slookups[tag]=tv +                end +              end +            end +          end +        end +        local mlookups=description.mlookups +        if mlookups then +          local tv=tables[mlookups] +          if tv then +            description.mlookups=tv +            mlookups=unpacked[tv] +          end +          if mlookups then +            for tag,list in next,mlookups do +              local tv=tables[list] +              if tv then +                mlookups[tag]=tv +                list=unpacked[tv] +              end +              if list then +                local what=lookuptypes[tag] +                if what=="pair" then +                  for i=1,#list do +                    local lookup=list[i] +                    local tv=tables[lookup[2]] +                    if tv then +                      lookup[2]=tv +                    end +                    local tv=tables[lookup[3]] +                    if tv then +                      lookup[3]=tv +                    end +                  end +                elseif what~="substitution" then +                  for i=1,#list do +                    local tv=tables[list[i]] +                    if tv then +                      list[i]=tv +                    end +                  end +                end +              end +            end +          end +        end +        local kerns=description.kerns +        if kerns then +          local tm=tables[kerns] +          if tm then +            description.kerns=tm +            kerns=unpacked[tm] +          end +          if kerns then +            for k,kern in next,kerns do +              local tv=tables[kern] +              if tv then +                kerns[k]=tv +              end +            end +          end +        end +        local math=description.math +        if math then +          local kerns=math.kerns +          if kerns then +            local tm=tables[kerns] +            if tm then +              math.kerns=tm +              kerns=unpacked[tm] +            end +            if kerns then +              for k,kern in next,kerns do +                local tv=tables[kern] +                if tv then +                  kerns[k]=tv +                end +              end +            end +          end +        end +        local anchors=description.anchors +        if anchors then +          local ta=tables[anchors] +          if ta then +            description.anchors=ta +            anchors=unpacked[ta] +          end +          if anchors then +            for tag,anchor in next,anchors do +              if tag=="baselig" then +                for _,list in next,anchor do +                  for i=1,#list do +                    local tv=tables[list[i]] +                    if tv then +                      list[i]=tv +                    end +                  end +                end +              else +                for a,data in next,anchor do +                  local tv=tables[data] +                  if tv then +                    anchor[a]=tv +                  end +                end +              end +            end +          end +        end +      end +      local lookups=data.lookups +      if lookups then +        for _,lookup in next,lookups do +          local rules=lookup.rules +          if rules then +            for i=1,#rules do  +              local rule=rules[i] +              local before=rule.before +              if before then +                local tv=tables[before] +                if tv then +                  rule.before=tv +                  before=unpacked[tv] +                end +                if before then +                  for i=1,#before do +                    local tv=tables[before[i]] +                    if tv then +                      before[i]=tv +                    end +                  end +                end +              end +              local after=rule.after +              if after then +                local tv=tables[after] +                if tv then +                  rule.after=tv +                  after=unpacked[tv] +                end +                if after then +                  for i=1,#after do +                    local tv=tables[after[i]] +                    if tv then +                      after[i]=tv +                    end +                  end +                end +              end +              local current=rule.current +              if current then +                local tv=tables[current] +                if tv then +                  rule.current=tv +                  current=unpacked[tv] +                end +                if current then +                  for i=1,#current do +                    local tv=tables[current[i]] +                    if tv then +                      current[i]=tv +                    end +                  end +                end +              end +              local replacements=rule.replacements +              if replacements then +                local tv=tables[replacements] +                if tv then +                  rule.replacements=tv +                end +              end +              local fore=rule.fore +              if fore then +                local tv=tables[fore] +                if tv then +                  rule.fore=tv +                end +              end +              local back=rule.back +              if back then +                local tv=tables[back] +                if tv then +                  rule.back=tv +                end +              end +              local names=rule.names +              if names then +                local tv=tables[names] +                if tv then +                  rule.names=tv +                end +              end +              local lookups=rule.lookups +              if lookups then +                local tv=tables[lookups] +                if tv then +                  rule.lookups=tv +                end +              end +            end +          end +        end +      end +      local anchor_to_lookup=resources.anchor_to_lookup +      if anchor_to_lookup then +        for anchor,lookup in next,anchor_to_lookup do +          local tv=tables[lookup] +          if tv then +            anchor_to_lookup[anchor]=tv +          end +        end +      end +      local lookup_to_anchor=resources.lookup_to_anchor +      if lookup_to_anchor then +        for lookup,anchor in next,lookup_to_anchor do +          local tv=tables[anchor] +          if tv then +            lookup_to_anchor[lookup]=tv +          end +        end +      end +      local ls=resources.sequences +      if ls then +        for _,feature in next,ls do +          local flags=feature.flags +          if flags then +            local tv=tables[flags] +            if tv then +              feature.flags=tv +            end +          end +          local subtables=feature.subtables +          if subtables then +            local tv=tables[subtables] +            if tv then +              feature.subtables=tv +            end +          end +          local features=feature.features +          if features then +            local tv=tables[features] +            if tv then +              feature.features=tv +              features=unpacked[tv] +            end +            if features then +              for script,data in next,features do +                local tv=tables[data] +                if tv then +                  features[script]=tv +                end +              end +            end +          end +        end +      end +      local lookups=resources.lookups +      if lookups then +        for _,lookup in next,lookups do +          local flags=lookup.flags +          if flags then +            local tv=tables[flags] +            if tv then +              lookup.flags=tv +            end +          end +          local subtables=lookup.subtables +          if subtables then +            local tv=tables[subtables] +            if tv then +              lookup.subtables=tv +            end +          end +        end +      end +      local features=resources.features +      if features then +        for _,what in next,glists do +          local feature=features[what] +          if feature then +            for tag,spec in next,feature do +              local tv=tables[spec] +              if tv then +                feature[tag]=tv +              end +            end +          end +        end +      end +      data.tables=nil +    end +  end +end +if otf.enhancers.register then +  otf.enhancers.register("pack",packdata) +  otf.enhancers.register("unpack",unpackdata) +end +otf.enhancers.unpack=unpackdata  + +end -- closure + +do -- begin closure to overcome local limits and interference +  if not modules then modules={} end modules ['luatex-fonts-lua']={    version=1.001,    comment="companion to luatex-*.tex", diff --git a/luaotfload-override.lua b/luaotfload-override.lua index 94f2376..4953edf 100644 --- a/luaotfload-override.lua +++ b/luaotfload-override.lua @@ -72,6 +72,9 @@ logs.names_report = function (mode, lvl, ...)      if loglevel > lvl then          if mode == "log" then              log (...) +        elseif mode == "both" then +            log (...) +            stdout (...)          else              stdout (...)          end diff --git a/luaotfload.dtx b/luaotfload.dtx index dbc822f..18e01d8 100644 --- a/luaotfload.dtx +++ b/luaotfload.dtx @@ -870,6 +870,7 @@ and the derived files  %                     \incitem{font-otf.lua} \incitem{font-otb.lua}  %                     \incitem{node-inj.lua} \incitem{font-ota.lua}  %                     \incitem{font-otn.lua} \incitem{font-def.lua} +%                     \incitem{font-otp.lua}  %                   \end{itemize}  %                 \end{multicols}  %   \end{itemize} @@ -907,21 +908,20 @@ and the derived files  %       \normalitem{\fileent{#1}}%  %       \space--\hskip1em  %     } -%     \ouritem {luaotfload-font-otc.lua} \fileent{font-otc} from \CONTEXT; -%                                  font feature handling. -%     \ouritem {luaotfload-lib-dir.lua}  \fileent{l-dir} from \CONTEXT; -%                                  contains functionality required -%                                  by \fileent{luaotfload-font-nms.lua}. -%     \ouritem {luaotfload-luat-ovr.lua} overrides the \CONTEXT logging -%                                  functionality. -%     \ouritem {luaotfload-font-pfb.lua} registers the \OpenType -%                                  font reader as handler for -%                                  Postscript fonts. -%     \ouritem {luaotfload-font-nms.lua} font database. -%     \ouritem {luaotfload-font-clr.lua} color handling. -%     \ouritem {luaotfload-font-ltx.lua} font feature handling. -%     \ouritem {luaotfload-features.lua} definitions of the \verb|anum| and -%                                  \verb|tlig| features. +%     \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 +%                                          font reader as handler for +%                                          Postscript fonts +%                                          (\abbrev{pfa}, \abbrev{pfb}). +%     \ouritem {luaotfload-database.lua}   font names database. +%     \ouritem {luaotfload-colors.lua}     color handling.  % \end{itemize}  %  % \begin{figure}[b] @@ -936,6 +936,10 @@ and the derived files  % version of this package before reporting a bug, as  % \identifier{luaotfload} is under active development and still a  % moving target. +% The development takes place on \identifier{github} at +% \url{https://github.com/lualatex/luaotfload} where there is an issue +% tracker for submitting bug reports, feature requests and the likes +% requests and the likes.  %  % Errors during database generation can be traced by increasing  % verbosity levels and redirecting log output to \fileent{stdout}: @@ -1038,13 +1042,19 @@ local error, warning, info, log =      luatexbase.provides_module(luaotfload.module)  %    \end{macrocode} -% -%    We set the minimum version requirement for \LUATEX to v0.74, as it was -%    the first version to include version 5.2 of the \LUA interpreter. +%    We set the minimum version requirement for \LUATEX to v0.76, +%    because the font loader requires recent features like direct +%    attribute indexing and \luafunction{node.end_of_math()} that aren’t +%    available in earlier versions.\footnote{% +%     See Taco’s announcement of v0.76: +%     \url{http://comments.gmane.org/gmane.comp.tex.luatex.user/4042} +%     and this commit by Hans that introduced those features. +%     \url{http://repo.or.cz/w/context.git/commitdiff/a51f6cf6ee087046a2ae5927ed4edff0a1acec1b}. +%   }  %  %    \begin{macrocode} -local luatex_version = 74 +local luatex_version = 76  if tex.luatexversion < luatex_version then      warning("LuaTeX v%.2f is old, v%.2f is recommended.", @@ -1107,7 +1117,6 @@ end  % \fileent{luaotfload-merged.lua}.  % If this file cannot be found, the original libraries from \CONTEXT of  % which the merged code was composed are loaded instead. -%  % The imported font loader will call \luafunction{callback.register} once  % while reading \fileent{font-def.lua}.  % This is unavoidable unless we modify the imported files, but harmless @@ -1265,6 +1274,7 @@ else--- the loading sequence is known to change, so this might have to      loadmodule('node-inj.lua')      loadmodule('font-ota.lua')      loadmodule('font-otn.lua') +    loadmodule('font-otp.lua')--- since 2013-04-23      loadmodule('luatex-fonts-lua.lua')      loadmodule('font-def.lua')      loadmodule('luatex-fonts-def.lua') @@ -1318,6 +1328,8 @@ add_to_callback("find_vf_file",  loadmodule"lib-dir.lua"   --- required by luaofload-database.lua  loadmodule"override.lua"  --- “luat-ovr” +logs.set_loglevel(0) +  %    \end{macrocode}  % \CONTEXT does not support ofm, these lines were added in order to make it  % work. However they do not seem necessary so they are commented for now. @@ -1339,9 +1351,17 @@ loadmodule"colors.lua"     --- “font-clr”  %    \end{macrocode}  % This hack makes fonts called with file method found by fonts.names.resolve -% instead of just trying to find them with kpse. It is necessary in case -% of fonts that are not accessible by kpse but present in the database, a -% quite common case under Linux. +% instead of just trying to find them with \identifier{kpse}. +% It is necessary in cases when font files are not reachable by +% \identifier{kpse} but present in the database, a quite common case +% under Linux. +% +% As of 2013-04-24 we have a workaround in the resolver that handles +% \verb|file:| lookups diverted this way. +% It requires some overhead due to additional extra data saved in the +% names database, and might vanish entirely once the font request syntax +% is understood. +% Until then it is considered a kludge, like the hack below.  %  %    \begin{macrocode} @@ -1389,7 +1409,7 @@ local define_font_wrapper = function (...)                  --- definers.read                  if stringfind(k, "Percent") then                      -- keep percent values as is -                    print(k,v) +                    --print(k,v)                      mathconstants[k] = v                  else                      mathconstants[k] = v / units_per_em * size @@ -1415,8 +1435,10 @@ end  %    \begin{macrocode}  local read_font_file = fonts.definers.read -local patch_defined_font = function (...) -    local tfmdata = read_font_file(...)-- spec -> size -> id -> tmfdata + +--- spec -> size -> id -> tmfdata +local patch_defined_font = function (specification, size, id) +    local tfmdata = read_font_file(specification, size, id)      if type(tfmdata) == "table" then          call_callback("luaotfload.patch_font", tfmdata)      end @@ -1486,7 +1508,6 @@ loadmodule"features.lua" --- contains what was “font-ltx” and “font-otc”  -- vim:tw=71:sw=4:ts=4:expandtab -  %    \end{macrocode}  %  % \iffalse diff --git a/tests/lookups.tex b/tests/lookups.tex index db26312..8b03d8e 100644 --- a/tests/lookups.tex +++ b/tests/lookups.tex @@ -5,10 +5,16 @@  \font\second=file:antpoltltsemiexpd-bolditalic.otf at 42pt  %% lookup font by name, with style in slash notation  \font\third={name:Antykwa torunska/I} at 42pt +%% unspecified lookup; in definers.read: +%%  - first it falls back to “file” +%%  - empty “method” field triggers fallback to “name” +%%  - names.resolve -> kpse.lookup -> hit! +\font\fourth=iwona at 42pt  {\first   foo \endgraf}  {\second  bar \endgraf}  {\third   baz \endgraf} +{\fourth  xyzzy \endgraf}  \bye  | 
