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 |