diff options
Diffstat (limited to 'Master/texmf-dist/tex/context/base/font-syn.lua')
-rw-r--r-- | Master/texmf-dist/tex/context/base/font-syn.lua | 549 |
1 files changed, 357 insertions, 192 deletions
diff --git a/Master/texmf-dist/tex/context/base/font-syn.lua b/Master/texmf-dist/tex/context/base/font-syn.lua index d9917771626..828808ec59d 100644 --- a/Master/texmf-dist/tex/context/base/font-syn.lua +++ b/Master/texmf-dist/tex/context/base/font-syn.lua @@ -6,57 +6,77 @@ if not modules then modules = { } end modules ['font-syn'] = { license = "see context related readme files" } +local next = next +local gsub, lower, match, find, lower, upper = string.gsub, string.lower, string.match, string.find, string.lower, string.upper + +local trace_names = false trackers.register("fonts.names", function(v) trace_names = v end) + --[[ldx-- <p>This module implements a name to filename resolver. Names are resolved using a table that has keys filtered from the font related files.</p> --ldx]]-- +local texsprint = (tex and tex.sprint) or print + fonts = fonts or { } input = input or { } texmf = texmf or { } -fonts.names = { } -fonts.names.filters = { } -fonts.names.data = { } -fonts.names.version = 1.04 -fonts.names.saved = false -fonts.names.loaded = false -fonts.names.be_clever = true -fonts.names.enabled = true -fonts.names.autoreload = toboolean(os.env['MTX.FONTS.AUTOLOAD'] or os.env['MTX_FONTS_AUTOLOAD'] or "no") -fonts.names.cache = containers.define("fonts","data",fonts.names.version,true) +fonts.names = fonts.names or { } +fonts.names.filters = fonts.names.filters or { } +fonts.names.data = fonts.names.data or { } + +local names = fonts.names +local filters = fonts.names.filters + +names.version = 1.08 -- when adapting this, also changed font-dum.lua +names.basename = "names" +names.saved = false +names.loaded = false +names.be_clever = true +names.enabled = true +names.autoreload = toboolean(os.env['MTX.FONTS.AUTOLOAD'] or os.env['MTX_FONTS_AUTOLOAD'] or "no") +names.cache = containers.define("fonts","data",names.version,true) --[[ldx-- <p>It would make sense to implement the filters in the related modules, but to keep the overview, we define them here.</p> --ldx]]-- -fonts.names.filters.otf = fontforge.info -fonts.names.filters.ttf = fontforge.info -fonts.names.filters.ttc = fontforge.info - -function fonts.names.filters.afm(name) - local f = io.open(name) - if f then - local hash = { } - for line in f:lines() do - local key, value = line:match("^(.+)%s+(.+)%s*$") - if key and #key > 0 then - hash[key:lower()] = value - end - if line:find("StartCharMetrics") then - break +filters.otf = fontloader.info +filters.ttf = fontloader.info +filters.ttc = fontloader.info +filters.dfont = fontloader.info + +function filters.afm(name) + -- we could parse the afm file as well, and then report an error but + -- it's not worth the trouble + local pfbname = resolvers.find_file(file.removesuffix(name)..".pfb","pfb") or "" + if pfbname == "" then + pfbname = resolvers.find_file(file.removesuffix(file.basename(name))..".pfb","pfb") or "" + end + if pfbname ~= "" then + local f = io.open(name) + if f then + local hash = { } + for line in f:lines() do + local key, value = match(line,"^(.+)%s+(.+)%s*$") + if key and #key > 0 then + hash[lower(key)] = value + end + if find(line,"StartCharMetrics") then + break + end end + f:close() + return hash end - f:close() - return hash - else - return nil end + return nil, "no matching pfb file" end -function fonts.names.filters.pfb(name) - return fontforge.info(name) +function filters.pfb(name) + return fontloader.info(name) end --[[ldx-- @@ -65,172 +85,320 @@ the file databases. Watch how we check not only for the names, but also for combination with the weight of a font.</p> --ldx]]-- -fonts.names.filters.list = { - "otf", "ttf", "ttc", "afm" -- pfb is quite messy, too many messages, maybe broken +filters.list = { + "otf", "ttf", "ttc", "dfont", "afm", } -fonts.names.filters.fixes = { +filters.fixes = { { "reg$", "regular", }, { "ita$", "italic", }, { "ital$", "italic", }, { "cond$", "condensed", }, + { "book$", "", }, } -fonts.names.xml_configuration_file = "fonts.conf" -- a bit weird format, bonus feature -fonts.names.environment_path_variable = "osfontdir" -- the official way, in minimals etc +names.xml_configuration_file = "fonts.conf" -- a bit weird format, bonus feature +names.environment_path_variable = "OSFONTDIR" -- the official way, in minimals etc + +filters.paths = { } +filters.names = { } -function fonts.names.getpaths(instance) +function names.getpaths(trace) local hash, result = { }, { } local function collect(t) for i=1, #t do - local v = input.clean_path(t[i]) - v = v:gsub("/+$","") - local key = v:lower() + local v = resolvers.clean_path(t[i]) + v = gsub(v,"/+$","") + local key = lower(v) if not hash[key] then hash[key], result[#result+1] = true, v end end end - local path = fonts.names.environment_path_variable - if path and path ~= "" then - collect(input.expanded_path_list(instance,path)) + local path = names.environment_path_variable or "" + if path ~= "" then + collect(resolvers.expanded_path_list(path)) end - local name = fonts.names.xml_configuration_file - if name and not name == "" then - local name = input.find_file(instance,name,"other") - if name ~= "" then - collect(xml.collect_texts(xml.load(name),"dir",true)) + if xml then + local confname = names.xml_configuration_file or "" + if confname ~= "" then + -- first look in the tex tree + local name = resolvers.find_file(confname,"other") + if name == "" then + -- after all, fontconfig is a unix thing + name = file.join("/etc",confname) + if not lfs.isfile(name) then + name = "" -- force quit + end + end + if name ~= "" and lfs.isfile(name) then + if trace then + logs.report("fontnames","loading fontconfig file: %s",name) + end + local xmldata = xml.load(name) + -- begin of untested mess + xml.include(xmldata,"include","",true,function(incname) + if not file.is_qualified_path(incname) then + local path = file.dirname(name) -- main name + if path ~= "" then + incname = file.join(path,incname) + end + end + if lfs.isfile(incname) then + if trace then + logs.report("fontnames","merging included fontconfig file: %s",incname) + end + return io.loaddata(incname) + elseif trace then + logs.report("fontnames","ignoring included fontconfig file: %s",incname) + end + end) + -- end of untested mess + local fontdirs = xml.collect_texts(xmldata,"dir",true) + if trace then + logs.report("fontnames","%s dirs found in fontconfig",#fontdirs) + end + collect(fontdirs) + end end end - function fonts.names.getpaths() + function names.getpaths() return result end return result end -function fonts.names.identify() - fonts.names.data = { +function names.cleanname(name) + return (gsub(lower(name),"[^%a%d]","")) +end + +function names.identify(verbose) -- lsr is for kpse + names.data = { + version = names.version, mapping = { }, - version = fonts.names.version + -- sorted = { }, + fallback_mapping = { }, + -- fallback_sorted = { }, } - local done, mapping, nofread, nofok = { }, fonts.names.data.mapping, 0, 0 - local function add(n,fontname,filename,suffix, sub) - n = n:lower() - if not mapping[n] then mapping[n], nofok = { suffix, fontname, filename, sub }, nofok + 1 end - n = n:gsub("[^%a%d]","") - if not mapping[n] then mapping[n], nofok = { suffix, fontname, filename, sub }, nofok + 1 end - end + local done, mapping, fallback_mapping, nofread, nofok = { }, names.data.mapping, names.data.fallback_mapping, 0, 0 + local cleanname = names.cleanname local function check(result, filename, suffix, is_sub) local fontname = result.fullname if fontname then - add(result.fullname, fontname, filename, suffix, is_sub) + local n = cleanname(result.fullname) + if not mapping[n] then + mapping[n], nofok = { lower(suffix), fontname, filename, is_sub }, nofok + 1 + end end if result.fontname then fontname = fontname or result.fontname - add(result.fontname, fontname, filename, suffix, is_sub) + local n = cleanname(result.fontname) + if not mapping[n] then + mapping[n], nofok = { lower(suffix), fontname, filename, is_sub }, nofok + 1 + end end - if result.familyname and result.weight then + if result.familyname and result.weight and result.italicangle == 0 then local madename = result.familyname .. " " .. result.weight fontname = fontname or madename - add(madename, fontname, filename, suffix, is_sub) + local n = cleanname(fontname) + if not mapping[n] and not fallback_mapping[n] then + fallback_mapping[n], nofok = { lower(suffix), fontname, filename, is_sub }, nofok + 1 + end end end - local function identify(completename,name,suffix) + local trace = verbose or trace_names + local skip_paths = filters.paths + local skip_names = filters.names + local function identify(completename,name,suffix,storedname) if not done[name] and io.exists(completename) then nofread = nofread + 1 - logs.info("fontnames", "identifying " .. suffix .. " font " .. completename) - logs.push() - local result = fonts.names.filters[suffix](completename) - logs.pop() + if #skip_paths > 0 then + local path = file.dirname(completename) + for i=1,#skip_paths do + if find(path,skip_paths[i]) then + if trace then + logs.report("fontnames","rejecting path of %s font %s",suffix,completename) + logs.push() + end + return + end + end + end + if #skip_names > 0 then + local base = file.basename(completename) + for i=1,#skip_paths do + if find(base,skip_names[i]) then + done[name] = true + if trace then + logs.report("fontnames","rejecting name of %s font %s",suffix,completename) + logs.push() + end + return + end + end + end + if trace_names then + logs.report("fontnames","identifying %s font %s",suffix,completename) + logs.push() + end + local result, message = filters[lower(suffix)](completename) + if trace then + logs.pop() + end if result then if not result[1] then - check(result,name,suffix,false) - else for _, r in ipairs(result) do - check(r,name,suffix,true) - end end + check(result,storedname,suffix,false) -- was name + else + for r=1,#result do + check(result[r],storedname,suffix,true) -- was name + end + end + if message and message ~= "" then + logs.report("fontnames","warning when identifying %s font %s: %s",suffix,completename,message) + end + else + logs.report("fontnames","error when identifying %s font %s: %s",suffix,completename,message or "unknown") end done[name] = true end end + local totalread, totalok = 0, 0 local function traverse(what, method) - for n, suffix in pairs(fonts.names.filters.list) do + for n, suffix in ipairs(filters.list) do nofread, nofok = 0, 0 local t = os.gettimeofday() -- use elapser - logs.report("fontnames", string.format("identifying %s font files with suffix %s",what,suffix)) + suffix = lower(suffix) + logs.report("fontnames", "identifying %s font files with suffix %s",what,suffix) method(suffix) - logs.report("fontnames", string.format("%s %s files identified, %s hash entries added, runtime %s seconds", nofread, what,nofok, os.gettimeofday()-t)) + suffix = upper(suffix) + logs.report("fontnames", "identifying %s font files with suffix %s",what,suffix) + method(suffix) + logs.report("fontnames", "%s %s files identified, %s hash entries added, runtime %0.3f seconds",nofread,what,nofok,os.gettimeofday()-t) + totalread, totalok = totalread + nofread, totalok + nofok end end - traverse("tree", function(suffix) - input.with_files(texmf.instance,".*%." .. suffix .. "$", function(method,root,path,name) - if method == "file" then - identify(root .."/" .. path .. "/" .. name,name,suffix) - end - end) - end) - traverse("system", function(suffix) - local pathlist = fonts.names.getpaths(texmf.instance) -- input.expanded_path_list(texmf.instance,"osfontdir") + local function walk_tree(pathlist,suffix) if pathlist then for _, path in ipairs(pathlist) do - path = input.clean_path(path .. "/") - path = path:gsub("/+","/") - local pattern = path .. "*." .. suffix - logs.report("fontnames", "globbing path " .. pattern) + path = resolvers.clean_path(path .. "/") + path = gsub(path,"/+","/") + local pattern = path .. "**." .. suffix -- ** forces recurse + logs.report("fontnames", "globbing path %s",pattern) local t = dir.glob(pattern) - for _, name in pairs(t) do -- ipairs - local mode = lfs.attributes(name,'mode') - if mode == "file" then - identify(name,file.basename(name),suffix) - end + for _, completename in pairs(t) do -- ipairs + identify(completename,file.basename(completename),suffix,completename) end end end + end + traverse("tree", function(suffix) -- TEXTREE only + resolvers.with_files(".*%." .. suffix .. "$", function(method,root,path,name) + if method == "file" then + local completename = root .."/" .. path .. "/" .. name + identify(completename,name,suffix,name,name) + end + end) end) + if texconfig.kpse_init then + -- we do this only for a stupid names run, not used for context itself, + -- using the vars is to clumsy so we just stick to a full scan instead + traverse("lsr", function(suffix) -- all trees + local pathlist = resolvers.split_path(resolvers.show_path("ls-R") or "") + walk_tree(pathlist,suffix) + end) + else + traverse("system", function(suffix) -- OSFONTDIR cum suis + walk_tree(names.getpaths(trace),suffix) + end) + end local t = { } - for _, f in ipairs(fonts.names.filters.fixes) do + for _, f in ipairs(filters.fixes) do local expression, replacement = f[1], f[2] - for k,v in pairs(mapping) do - local fix, pos = k:gsub(expression,replacement) + for k,v in next, mapping do + local fix, pos = gsub(k,expression,replacement) if pos > 0 and not mapping[fix] then t[fix] = v end end end - for k,v in pairs(t) do + local n = 0 + for k,v in next, t do mapping[k] = v + n = n + 1 + end + local rejected = 0 + for k, v in next, mapping do + local kind, filename = v[1], v[3] + if not file.is_qualified_path(filename) and resolvers.find_file(filename,kind) == "" then + mapping[k] = nil + rejected = rejected + 1 + end + end + if n > 0 then + logs.report("fontnames", "%s files read, %s normal and %s extra entries added, %s rejected, %s valid",totalread,totalok,n,rejected,totalok+n-rejected) end end -function fonts.names.load(reload) - if not fonts.names.loaded then +function names.is_permitted(name) + return containers.is_usable(names.cache(), name) +end +function names.write_data(name,data) + containers.write(names.cache(),name,data) +end +function names.read_data(name) + return containers.read(names.cache(),name) +end + +function names.load(reload,verbose) + if not names.loaded then if reload then - if containers.is_usable(fonts.names.cache(), "names") then - fonts.names.identify() - containers.write(fonts.names.cache(), "names", fonts.names.data) + if names.is_permitted(names.basename) then + names.identify(verbose) + names.write_data(names.basename,names.data) + else + logs.report("font table", "unable to access database cache") end - fonts.names.saved = true + names.saved = true else - fonts.names.data = containers.read(fonts.names.cache(), "names") - if not fonts.names.saved then - if table.is_empty(fonts.names.data) or table.is_empty(fonts.names.data.mapping) then - fonts.names.load(true) + names.data = names.read_data(names.basename) + if not names.saved then + if table.is_empty(names.data) or table.is_empty(names.data.mapping) then + names.load(true) end - fonts.names.saved = true + names.saved = true end end - fonts.names.loaded = true + local data = names.data + if data then + data.sorted = table.sortedkeys(data.mapping or { }) or { } + data.fallback_sorted = table.sortedkeys(data.fallback_mapping or { }) or { } + else + logs.report("font table", "accessing the data table failed") + end + names.loaded = true end end -function fonts.names.list(pattern,reload) - fonts.names.load(reload) - if fonts.names.loaded then +function names.list(pattern,reload) + names.load(reload) + if names.loaded then local t = { } - for k,v in pairs(fonts.names.data.mapping) do - if k:find(pattern) then - t[k] = v + local function list_them(mapping,sorted) + if mapping[pattern] then + t[pattern] = mapping[pattern] + else + for k,v in ipairs(sorted) do + if find(v,pattern) then + t[v] = mapping[v] + end + end end end + local data = names.data + if data then + list_them(data.mapping,data.sorted) + list_them(data.fallback_mapping,data.fallback_sorted) + end return t else return nil @@ -243,99 +411,81 @@ here is for testing purposes only (it deals with names prefixed by an encoding name).</p> --ldx]]-- -do - - local function found(name) - if fonts.names.data then - local result, mapping = nil, fonts.names.data.mapping - local mn = mapping[name] - if mn then - return mn[2], mn[3], mn[4] - end - if fonts.names.be_clever then -- this will become obsolete - local encoding, tag = name:match("^(.-)[%-%:](.+)$") - local mt = mapping[tag] - if tag and fonts.enc.is_known(encoding) and mt then - return mt[1], encoding .. "-" .. mt[3], mt[4] - end - end - -- name, type, file - for k,v in pairs(mapping) do - if k:find(name) then - return v[2], v[3], v[4] - end - end - local condensed = name:gsub("[^%a%d]","") - local mc = mapping[condensed] - if mc then - return mc[2], mc[3], mc[4] - end - for k,v in pairs(mapping) do - if k:find(condensed) then - return v[2], v[3], v[4] - end - end +local function found_indeed(mapping,sorted,name) + local mn = mapping[name] + if mn then + return mn[2], mn[3], mn[4] + end + if names.be_clever then -- this will become obsolete + local encoding, tag = match(name,"^(.-)[%-%:](.+)$") + local mt = mapping[tag] + if tag and fonts.enc.is_known(encoding) and mt then + return mt[1], encoding .. "-" .. mt[3], mt[4] end - return nil, nil, nil end - - local reloaded = false - - function fonts.names.resolve(askedname, sub) - if not askedname then - return nil, nil - elseif fonts.names.enabled then - askedname = askedname:lower() - fonts.names.load() - local name, filename, is_sub = found(askedname) - if not filename and not reloaded and fonts.names.autoreload then - fonts.names.loaded = false - reloaded = true - io.flush() - fonts.names.load(true) - name, filename, is_sub = found(askedname) - end - if is_sub then - return filename, name - else - return filename, sub - end - else - return filename, sub + -- name, type, file + for k,v in next, mapping do + if find(k,name) then + return v[2], v[3], v[4] end end + local condensed = gsub(name,"[^%a%d]","") + local mc = mapping[condensed] + if mc then + return mc[2], mc[3], mc[4] + end + for k=1,#sorted do + local v = sorted[k] + if find(v,condensed) then + v = mapping[v] + return v[2], v[3], v[4] + end + end + return nil, nil, nil +end +local function found(name) + if name and name ~= "" and names.data then + name = names.cleanname(name) + local data = names.data + local fontname, filename, is_sub = found_indeed(data.mapping, data.sorted, name) + if not fontname or not filename then + fontname, filename, is_sub = found_indeed(data.fallback_mapping, data.fallback_sorted, name) + end + return fontname, filename, is_sub + else + return nil, nil, nil + end end ---[[ldx-- -<p>A handy helper.</p> ---ldx]]-- +local reloaded = false -function fonts.names.table(pattern,reload,all) - local t = fonts.names.list(pattern,reload) - if t then - tex.sprint(tex.ctxcatcodes,"\\start\\nonknuthmode\\starttabulate[|T|T|T|T|T|]") - tex.sprint(tex.ctxcatcodes,"\\NC hashname\\NC type\\NC fontname\\NC filename\\NC\\NR\\HL") - for k,v in pairs(table.sortedkeys(t)) do - if all or v == t[v][2]:lower() then - local type, name, file = unpack(t[v]) - if type and name and file then - tex.sprint(tex.ctxcatcodes,string.format("\\NC %s\\NC %s\\NC %s\\NC %s\\NC\\NR",v,type, name, file)) - else - logs.report("font table", "skipping ".. v) - end - end +function names.specification(askedname, sub) + if askedname and askedname ~= "" and names.enabled then + askedname = lower(askedname) + names.load() + local name, filename, is_sub = found(askedname) + if not filename and not reloaded and names.autoreload then + names.loaded = false + reloaded = true + io.flush() + names.load(true) + name, filename, is_sub = found(askedname) end - tex.sprint(tex.ctxcatcodes,"\\stoptabulate\\stop") + return name, filename, is_sub end end +function names.resolve(askedname, sub) + local name, filename, is_sub = names.specification(askedname, sub) + return filename, (is_sub and name) or sub +end --[[ldx-- <p>Fallbacks, not permanent but a transition thing.</p> --ldx]]-- -fonts.names.new_to_old = { +names.new_to_old = { ["lmroman10-capsregular"] = "lmromancaps10-oblique", ["lmroman10-capsoblique"] = "lmromancaps10-regular", ["lmroman10-demi"] = "lmromandemi10-oblique", @@ -377,4 +527,19 @@ fonts.names.new_to_old = { ["lmtypewritervarwd10-darkoblique"] = "lmmonoproplt10-boldoblique", } -fonts.names.old_to_new = table.swapped(fonts.names.new_to_old) +names.old_to_new = table.swapped(names.new_to_old) + +function names.exists(name) + local fna, found = names.autoreload, false + names.autoreload = false + for k,v in ipairs(filters.list) do + found = (resolvers.find_file(name,v) or "") ~= "" + if found then + break + end + end + found = found or (resolvers.find_file(name,"tfm") or "") ~= "" + found = found or (names.resolve(name) or "") ~= "" + names.autoreload = fna + return found +end |