if not modules then modules = { } end modules ['font-syn'] = { version = 1.001, comment = "companion to font-ini.mkiv", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files" } -- todo: subs in lookups requests local next, tonumber, type, tostring = next, tonumber, type, tostring local sub, gsub, lower, match, find, lower, upper = string.sub, string.gsub, string.lower, string.match, string.find, string.lower, string.upper local find, gmatch = string.find, string.gmatch local concat, sort, format = table.concat, table.sort, string.format local serialize, sortedhash = table.serialize, table.sortedhash local lpegmatch = lpeg.match local unpack = unpack or table.unpack local formatters, topattern = string.formatters, string.topattern local allocate = utilities.storage.allocate local sparse = utilities.storage.sparse local setmetatableindex = table.setmetatableindex local removesuffix = file.removesuffix local splitbase = file.splitbase local splitname = file.splitname local basename = file.basename local nameonly = file.nameonly local pathpart = file.pathpart local filejoin = file.join local is_qualified_path = file.is_qualified_path local exists = io.exists local findfile = resolvers.findfile local cleanpath = resolvers.cleanpath local resolveprefix = resolvers.resolve local fontloader = fontloader local font_to_table = fontloader.to_table local open_font = fontloader.open local get_font_info = fontloader.info local close_font = fontloader.close local font_fields = fontloader.fields local settings_to_hash = utilities.parsers.settings_to_hash_tolerant local trace_names = false trackers.register("fonts.names", function(v) trace_names = v end) local trace_warnings = false trackers.register("fonts.warnings", function(v) trace_warnings = v end) local trace_specifications = false trackers.register("fonts.specifications", function(v) trace_specifications = v end) local report_names = logs.reporter("fonts","names") --[[ldx--
This module implements a name to filename resolver. Names are resolved using a table that has keys filtered from the font related files.
--ldx]]-- fonts = fonts or { } -- also used elsewhere local names = fonts.names or allocate { } fonts.names = names local filters = names.filters or { } names.filters = filters local treatments = fonts.treatments or { } fonts.treatments = treatments names.data = names.data or allocate { } names.version = 1.123 names.basename = "names" names.saved = false names.loaded = false names.be_clever = true names.enabled = true names.cache = containers.define("fonts","data",names.version,true) local usesystemfonts = true local autoreload = true directives.register("fonts.autoreload", function(v) autoreload = toboolean(v) end) directives.register("fonts.usesystemfonts", function(v) usesystemfonts = toboolean(v) end) --[[ldx--A few helpers.
--ldx]]-- local P, C, Cc, Cs = lpeg.P, lpeg.C, lpeg.Cc, lpeg.Cs -- -- what to do with these -- -- -- -- thin -> thin -- -- regu -> regular -> normal -- norm -> normal -> normal -- stan -> standard -> normal -- medi -> medium -- ultr -> ultra -- ligh -> light -- heav -> heavy -- blac -> black -- thin -- book -- verylight -- -- buch -> book -- buchschrift -> book -- halb -> demi -- halbfett -> demi -- mitt -> medium -- mittel -> medium -- fett -> bold -- mage -> light -- mager -> light -- nord -> normal -- gras -> normal local weights = Cs ( -- not extra P("demibold") + P("semibold") + P("mediumbold") + P("ultrabold") + P("extrabold") + P("ultralight") + P("extralight") + P("bold") + P("demi") + P("semi") + P("light") + P("medium") + P("heavy") + P("ultra") + P("black") --+ P("bol") / "bold" -- blocks + P("bol") + P("regular") / "normal" ) -- numeric_weights = { -- 200 = "extralight", -- 300 = "light", -- 400 = "book", -- 500 = "medium", -- 600 = "demi", -- 700 = "bold", -- 800 = "heavy", -- 900 = "black", -- } local normalized_weights = sparse { regular = "normal", } local styles = Cs ( P("reverseoblique") / "reverseitalic" + P("regular") / "normal" + P("italic") + P("oblique") / "italic" + P("slanted") + P("roman") / "normal" + P("ital") / "italic" -- might be tricky + P("ita") / "italic" -- might be tricky --+ P("obli") / "oblique" ) local normalized_styles = sparse { reverseoblique = "reverseitalic", regular = "normal", oblique = "italic", } local widths = Cs( P("condensed") + P("thin") + P("expanded") + P("cond") / "condensed" --+ P("expa") / "expanded" + P("normal") + P("book") / "normal" ) local normalized_widths = sparse() local variants = Cs( -- fax casual P("smallcaps") + P("oldstyle") + P("caps") / "smallcaps" ) local normalized_variants = sparse() names.knownweights = { "black", "bold", "demi", "demibold", "extrabold", "heavy", "light", "medium", "mediumbold", "normal", "regular", "semi", "semibold", "ultra", "ultrabold", "ultralight", } names.knownstyles = { "italic", "normal", "oblique", "regular", "reverseitalic", "reverseoblique", "roman", "slanted", } names.knownwidths = { "book", "condensed", "expanded", "normal", "thin", } names.knownvariants = { "normal", "oldstyle", "smallcaps", } local remappedweights = { [""] = "normal", ["bol"] = "bold", } local remappedstyles = { [""] = "normal", } local remappedwidths = { [""] = "normal", } local remappedvariants = { [""] = "normal", } names.remappedweights = remappedweights setmetatableindex(remappedweights ,"self") names.remappedstyles = remappedstyles setmetatableindex(remappedstyles ,"self") names.remappedwidths = remappedwidths setmetatableindex(remappedwidths ,"self") names.remappedvariants = remappedvariants setmetatableindex(remappedvariants,"self") local any = P(1) local analyzed_table local analyzer = Cs ( ( weights / function(s) analyzed_table[1] = s return "" end + styles / function(s) analyzed_table[2] = s return "" end + widths / function(s) analyzed_table[3] = s return "" end + variants / function(s) analyzed_table[4] = s return "" end + any )^0 ) local splitter = lpeg.splitat("-") function names.splitspec(askedname) local name, weight, style, width, variant = lpegmatch(splitter,askedname) weight = weight and lpegmatch(weights, weight) or weight style = style and lpegmatch(styles, style) or style width = width and lpegmatch(widths, width) or width variant = variant and lpegmatch(variants,variant) or variant if trace_names then report_names("requested name %a split in name %a, weight %a, style %a, width %a and variant %a", askedname,name,weight,style,width,variant) end if not weight or not weight or not width or not variant then weight, style, width, variant = weight or "normal", style or "normal", width or "normal", variant or "normal" if trace_names then report_names("request %a normalized to '%s-%s-%s-%s-%s'", askedname,name,weight,style,width,variant) end end return name or askedname, weight, style, width, variant end local function analyzespec(somename) if somename then analyzed_table = { } local name = lpegmatch(analyzer,somename) return name, analyzed_table[1], analyzed_table[2], analyzed_table[3], analyzed_table[4] end end --[[ldx--It would make sense to implement the filters in the related modules, but to keep the overview, we define them here.
--ldx]]-- filters.otf = get_font_info filters.ttf = get_font_info filters.ttc = get_font_info filters.dfont = get_font_info -- We had this as temporary solution because we needed a bit more info but in the -- meantime it got an interesting side effect: currently luatex delays loading of e.g. -- glyphs so here we first load and then discard which is a waste. In the past it did -- free memory because a full load was done. One of these things that goes unnoticed. -- -- missing: names, units_per_em, design_range_bottom, design_range_top, design_size, -- pfminfo, top_side_bearing -- local function get_full_info(...) -- check with taco what we get / could get -- local ff = open_font(...) -- if ff then -- local d = ff -- and font_to_table(ff) -- d.glyphs, d.subfonts, d.gpos, d.gsub, d.lookups = nil, nil, nil, nil, nil -- close_font(ff) -- return d -- else -- return nil, "error in loading font" -- end -- end -- Phillip suggested this faster variant but it's still a hack as fontloader.info should -- return these keys/values (and maybe some more) but at least we close the loader which -- might save some memory in the end. -- local function get_full_info(name) -- local ff = open_font(name) -- if ff then -- local fields = table.tohash(font_fields(ff),true) -- isn't that one stable -- local d = { -- names = fields.names and ff.names, -- familyname = fields.familyname and ff.familyname, -- fullname = fields.fullname and ff.fullname, -- fontname = fields.fontname and ff.fontname, -- weight = fields.weight and ff.weight, -- italicangle = fields.italicangle and ff.italicangle, -- units_per_em = fields.units_per_em and ff.units_per_em, -- design_range_bottom = fields.design_range_bottom and ff.design_range_bottom, -- design_range_top = fields.design_range_top and ff.design_range_top, -- design_size = fields.design_size and ff.design_size, -- italicangle = fields.italicangle and ff.italicangle, -- pfminfo = fields.pfminfo and ff.pfminfo, -- top_side_bearing = fields.top_side_bearing and ff.top_side_bearing, -- } -- setmetatableindex(d,function(t,k) -- report_names("warning, trying to access field %a in font table of %a",k,name) -- end) -- close_font(ff) -- return d -- else -- return nil, "error in loading font" -- end -- end -- more efficient: local fields = nil local function get_full_info(name) local ff = open_font(name) if ff then if not fields then fields = table.tohash(font_fields(ff),true) end -- unfortunately luatex aborts when a field is not available local d = { names = fields.names and ff.names, familyname = fields.familyname and ff.familyname, fullname = fields.fullname and ff.fullname, fontname = fields.fontname and ff.fontname, weight = fields.weight and ff.weight, italicangle = fields.italicangle and ff.italicangle, units_per_em = fields.units_per_em and ff.units_per_em, design_range_bottom = fields.design_range_bottom and ff.design_range_bottom, design_range_top = fields.design_range_top and ff.design_range_top, design_size = fields.design_size and ff.design_size, italicangle = fields.italicangle and ff.italicangle, pfminfo = fields.pfminfo and ff.pfminfo, top_side_bearing = fields.top_side_bearing and ff.top_side_bearing, -- not there } setmetatableindex(d,function(t,k) report_names("warning, trying to access field %a in font table of %a",k,name) end) close_font(ff) return d else return nil, "error in loading font" end end -- As we have lazy loading anyway, this one still is full and with less code than -- the previous one. But this depends on the garbage collector to kick in and in the -- current version that somehow happens not that often (on my machine I end up with -- soem 3 GB extra before that happens). -- local function get_full_info(...) -- local ff = open_font(...) -- if ff then -- local d = { } -- ff is userdata so [1] or # fails on it -- setmetatableindex(d,ff) -- return d -- garbage collection will do the close_font(ff) -- else -- return nil, "error in loading font" -- end -- end fontloader.fullinfo = get_full_info filters .otf = get_full_info filters .ttf = get_full_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 = findfile(removesuffix(name)..".pfb","pfb") or "" if pfbname == "" then pfbname = findfile(nameonly(name)..".pfb","pfb") or "" end if pfbname ~= "" then local f = io.open(name) if f then local hash = { } for line in f:lines() do -- slow local key, value = match(line,"^(.+)%s+(.+)%s*$") if key and #key > 0 then hash[lower(key)] = value end if find(line,"StartCharMetrics",1,true) then break end end f:close() return hash end end return nil, "no matching pfb file" end function filters.pfb(name) return get_font_info(name) end --[[ldx--The scanner loops over the filters using the information stored in the file databases. Watch how we check not only for the names, but also for combination with the weight of a font.
--ldx]]-- filters.list = { "otf", "ttf", "ttc", "dfont", "afm", -- "ttc", "otf", "ttf", "dfont", "afm", } names.fontconfigfile = "fonts.conf" -- a bit weird format, bonus feature names.osfontdirvariable = "OSFONTDIR" -- the official way, in minimals etc filters.paths = { } filters.names = { } function names.getpaths(trace) local hash, result, r = { }, { }, 0 local function collect(t,where) for i=1,#t do local v = cleanpath(t[i]) v = gsub(v,"/+$","") -- not needed any more local key = lower(v) report_names("%a specifies path %a",where,v) if not hash[key] then r = r + 1 result[r] = v hash[key] = true end end end local path = names.osfontdirvariable or "" if path ~= "" then collect(resolvers.expandedpathlist(path),path) end if xml then local confname = resolvers.expansion("FONTCONFIG_FILE") or "" if confname == "" then confname = names.fontconfigfile or "" end if confname ~= "" then -- first look in the tex tree local name = findfile(confname,"fontconfig files") or "" if name == "" then -- after all, fontconfig is a unix thing name = filejoin("/etc",confname) if not lfs.isfile(name) then name = "" -- force quit end end if name ~= "" and lfs.isfile(name) then if trace_names then report_names("%s fontconfig file %a","loading",name) end local xmldata = xml.load(name) -- begin of untested mess xml.include(xmldata,"include","",true,function(incname) if not is_qualified_path(incname) then local path = pathpart(name) -- main name if path ~= "" then incname = filejoin(path,incname) end end if lfs.isfile(incname) then if trace_names then report_names("%s fontconfig file %a","merging included",incname) end return io.loaddata(incname) elseif trace_names then report_names("%s fontconfig file: %a","ignoring included",incname) end end) -- end of untested mess local fontdirs = xml.collect_texts(xmldata,"dir",true) if trace_names then report_names("%s dirs found in fontconfig",#fontdirs) end collect(fontdirs,"fontconfig file") end end end function names.getpaths() return result end return result end local function cleanname(name) return (gsub(lower(name),"[^%a%d]","")) end local function cleanfilename(fullname,defaultsuffix) local path, name, suffix = splitname(fullname) name = gsub(lower(name),"[^%a%d]","") if suffix and suffix ~= "" then return name .. ".".. suffix elseif defaultsuffix and defaultsuffix ~= "" then return name .. ".".. defaultsuffix else return name end end names.cleanname = cleanname names.cleanfilename = cleanfilename local function check_names(result) local names = result.names if names then for i=1,#names do local name = names[i] if name.lang == "English (US)" then return name.names end end end end local function walk_tree(pathlist,suffix,identify) if pathlist then for i=1,#pathlist do local path = pathlist[i] path = cleanpath(path .. "/") path = gsub(path,"/+","/") local pattern = path .. "**." .. suffix -- ** forces recurse report_names("globbing path %a",pattern) local t = dir.glob(pattern) sort(t,sorter) for j=1,#t do local completename = t[j] identify(completename,basename(completename),suffix,completename) end end end end local function check_name(data,result,filename,modification,suffix,subfont) -- shortcuts local specifications = data.specifications -- prepare local names = check_names(result) -- fetch local familyname = names and names.preffamilyname or result.familyname local fullname = names and names.fullname or result.fullname local fontname = result.fontname local subfamily = names and names.subfamily local modifiers = names and names.prefmodifiers local weight = names and names.weight or result.weight local italicangle = tonumber(result.italicangle) local subfont = subfont or nil local rawname = fullname or fontname or familyname local filebase = removesuffix(basename(filename)) local cleanfilename = cleanname(filebase) -- for WS -- normalize familyname = familyname and cleanname(familyname) fullname = fullname and cleanname(fullname) fontname = fontname and cleanname(fontname) subfamily = subfamily and cleanname(subfamily) modifiers = modifiers and cleanname(modifiers) weight = weight and cleanname(weight) italicangle = italicangle == 0 and nil -- analyze local a_name, a_weight, a_style, a_width, a_variant = analyzespec(fullname or fontname or familyname) -- check local width = a_width local variant = a_variant local style = modifiers and gsub(modifiers,"[^%a]","") if not style and italicangle then style = "italic" end if not variant or variant == "" then variant = "normal" end if not weight or weight == "" then weight = a_weight end if not style or style == "" then style = a_style end if not familyname then familyname = a_name end fontname = fontname or fullname or familyname or filebase -- maybe cleanfilename fullname = fullname or fontname familyname = familyname or fontname -- we do these sparse local units = result.units_per_em or 1000 -- can be zero too local minsize = result.design_range_bottom or 0 local maxsize = result.design_range_top or 0 local designsize = result.design_size or 0 local angle = result.italicangle or 0 local pfminfo = result.pfminfo local pfmwidth = pfminfo and pfminfo.width or 0 local pfmweight = pfminfo and pfminfo.weight or 0 -- specifications[#specifications + 1] = { filename = filename, -- unresolved cleanfilename = cleanfilename, format = lower(suffix), subfont = subfont, rawname = rawname, familyname = familyname, fullname = fullname, fontname = fontname, subfamily = subfamily, modifiers = modifiers, weight = weight, style = style, width = width, variant = variant, units = units ~= 1000 and units or nil, pfmwidth = pfmwidth ~= 0 and pfmwidth or nil, pfmweight = pfmweight ~= 0 and pfmweight or nil, angle = angle ~= 0 and angle or nil, minsize = minsize ~= 0 and minsize or nil, maxsize = maxsize ~= 0 and maxsize or nil, designsize = designsize ~= 0 and designsize or nil, modification = modification ~= 0 and modification or nil, } -- inspect(filename) -- inspect(result) -- inspect(specifications[#specifications]) end local function cleanupkeywords() local data = names.data local specifications = names.data.specifications if specifications then local weights = { } local styles = { } local widths = { } local variants = { } for i=1,#specifications do local s = specifications[i] -- fix (sofar styles are taken from the name, and widths from the specification) local _, b_weight, b_style, b_width, b_variant = analyzespec(s.weight) local _, c_weight, c_style, c_width, c_variant = analyzespec(s.style) local _, d_weight, d_style, d_width, d_variant = analyzespec(s.width) local _, e_weight, e_style, e_width, e_variant = analyzespec(s.variant) local _, f_weight, f_style, f_width, f_variant = analyzespec(s.fullname or "") local weight = b_weight or c_weight or d_weight or e_weight or f_weight or "normal" local style = b_style or c_style or d_style or e_style or f_style or "normal" local width = b_width or c_width or d_width or e_width or f_width or "normal" local variant = b_variant or c_variant or d_variant or e_variant or f_variant or "normal" weight = remappedweights [weight or ""] style = remappedstyles [style or ""] width = remappedwidths [width or ""] variant = remappedvariants[variant or ""] weights [weight ] = (weights [weight ] or 0) + 1 styles [style ] = (styles [style ] or 0) + 1 widths [width ] = (widths [width ] or 0) + 1 variants[variant] = (variants[variant] or 0) + 1 if weight ~= s.weight then s.fontweight = s.weight end s.weight, s.style, s.width, s.variant = weight, style, width, variant end local stats = data.statistics stats.used_weights, stats.used_styles, stats.used_widths, stats.used_variants = weights, styles, widths, variants end end local function collectstatistics() local data = names.data local specifications = data.specifications if specifications then local f_w = formatters["%i"] local f_a = formatters["%0.2f"] -- normal stuff local weights = { } local styles = { } local widths = { } local variants = { } -- weird stuff local angles = { } -- extra stuff local pfmweights = { } setmetatableindex(pfmweights,"table") local pfmwidths = { } setmetatableindex(pfmwidths, "table") -- main loop for i=1,#specifications do local s = specifications[i] -- normal stuff local weight = s.weight local style = s.style local width = s.width local variant = s.variant if weight then weights [weight ] = (weights [weight ] or 0) + 1 end if style then styles [style ] = (styles [style ] or 0) + 1 end if width then widths [width ] = (widths [width ] or 0) + 1 end if variant then variants[variant] = (variants[variant] or 0) + 1 end -- weird stuff local angle = f_a(tonumber(s.angle) or 0) angles[angle] = (angles[angles] or 0) + 1 -- extra stuff local pfmweight = f_w(s.pfmweight or 0) local pfmwidth = f_w(s.pfmwidth or 0) local tweights = pfmweights[pfmweight] local twidths = pfmwidths [pfmwidth] tweights[pfmweight] = (tweights[pfmweight] or 0) + 1 twidths[pfmwidth] = (twidths [pfmwidth] or 0) + 1 end -- local stats = data.statistics stats.weights = weights stats.styles = styles stats.widths = widths stats.variants = variants stats.angles = angles stats.pfmweights = pfmweights stats.pfmwidths = pfmwidths stats.fonts = #specifications -- setmetatableindex(pfmweights,nil) setmetatableindex(pfmwidths, nil) -- report_names("") report_names("weights") report_names("") report_names(formatters[" %T"](weights)) report_names("") report_names("styles") report_names("") report_names(formatters[" %T"](styles)) report_names("") report_names("widths") report_names("") report_names(formatters[" %T"](widths)) report_names("") report_names("variants") report_names("") report_names(formatters[" %T"](variants)) report_names("") report_names("angles") report_names("") report_names(formatters[" %T"](angles)) report_names("") report_names("pfmweights") report_names("") for k, v in sortedhash(pfmweights) do report_names(formatters[" %-10s: %T"](k,v)) end report_names("") report_names("pfmwidths") report_names("") for k, v in sortedhash(pfmwidths) do report_names(formatters[" %-10s: %T"](k,v)) end report_names("") end end local function collecthashes() local data = names.data local mappings = data.mappings local fallbacks = data.fallbacks local specifications = data.specifications local nofmappings = 0 local noffallbacks = 0 if specifications then -- maybe multiple passes for index=1,#specifications do local s = specifications[index] local format, fullname, fontname, familyname, weight, subfamily = s.format, s.fullname, s.fontname, s.familyname, s.weight, s.subfamily local mf, ff = mappings[format], fallbacks[format] if fullname and not mf[fullname] then mf[fullname], nofmappings = index, nofmappings + 1 end if fontname and not mf[fontname] then mf[fontname], nofmappings = index, nofmappings + 1 end if familyname and weight and weight ~= sub(familyname,#familyname-#weight+1,#familyname) then local madename = familyname .. weight if not mf[madename] and not ff[madename] then ff[madename], noffallbacks = index, noffallbacks + 1 end end if familyname and subfamily and subfamily ~= sub(familyname,#familyname-#subfamily+1,#familyname) then local extraname = familyname .. subfamily if not mf[extraname] and not ff[extraname] then ff[extraname], noffallbacks = index, noffallbacks + 1 end end if familyname and not mf[familyname] and not ff[familyname] then ff[familyname], noffallbacks = index, noffallbacks + 1 end end end return nofmappings, noffallbacks end local function collectfamilies() local data = names.data local specifications = data.specifications local families = data.families for index=1,#specifications do local familyname = specifications[index].familyname local family = families[familyname] if not family then families[familyname] = { index } else family[#family+1] = index end end end local function checkduplicate(where) -- fails on "Romantik" but that's a border case anyway local data = names.data local mapping = data[where] local specifications = data.specifications local loaded = { } if specifications and mapping then -- was: for _, m in sortedhash(mapping) do local order = filters.list for i=1,#order do local m = mapping[order[i]] for k, v in sortedhash(m) do local s = specifications[v] local hash = formatters["%s-%s-%s-%s-%s"](s.familyname,s.weight or "*",s.style or "*",s.width or "*",s.variant or "*") local h = loaded[hash] if h then local ok = true local fn = s.filename for i=1,#h do local hn = s.filename if h[i] == fn then ok = false break end end if ok then h[#h+1] = fn end else loaded[hash] = { s.filename } end end end end local n = 0 for k, v in sortedhash(loaded) do local nv = #v if nv > 1 then if trace_warnings then report_names("lookup %a clashes with %a",k,v) end n = n + nv end end report_names("%a double lookups in %a",n,where) end local function checkduplicates() checkduplicate("mappings") checkduplicate("fallbacks") end local sorter = function(a,b) return a > b -- to be checked end local function sorthashes() local data = names.data local list = filters.list local mappings = data.mappings local fallbacks = data.fallbacks local sorted_mappings = { } local sorted_fallbacks = { } data.sorted_mappings = sorted_mappings data.sorted_fallbacks = sorted_fallbacks for i=1,#list do local l = list[i] sorted_mappings [l] = table.keys(mappings[l]) sorted_fallbacks[l] = table.keys(fallbacks[l]) sort(sorted_mappings [l],sorter) sort(sorted_fallbacks[l],sorter) end data.sorted_families = table.keys(data.families) sort(data.sorted_families,sorter) end local function unpackreferences() local data = names.data local specifications = data.specifications if specifications then for k, v in next, data.families do for i=1,#v do v[i] = specifications[v[i]] end end local mappings = data.mappings if mappings then for _, m in next, mappings do for k, v in next, m do m[k] = specifications[v] end end end local fallbacks = data.fallbacks if fallbacks then for _, f in next, fallbacks do for k, v in next, f do f[k] = specifications[v] end end end end end local function analyzefiles(olddata) if not trace_warnings then report_names("warnings are disabled (tracker 'fonts.warnings')") end local data = names.data local done = { } local totalnofread = 0 local totalnofskipped = 0 local totalnofduplicates = 0 local nofread = 0 local nofskipped = 0 local nofduplicates = 0 local skip_paths = filters.paths local skip_names = filters.names local specifications = data.specifications local oldindices = olddata and olddata.indices or { } local oldspecifications = olddata and olddata.specifications or { } local oldrejected = olddata and olddata.rejected or { } local treatmentdata = treatments.data or { } -- when used outside context local function identify(completename,name,suffix,storedname) local pathpart, basepart = splitbase(completename) nofread = nofread + 1 local treatment = treatmentdata[completename] or treatmentdata[basepart] if treatment and treatment.ignored then if trace_names then report_names("%s font %a is ignored, reason %a",suffix,completename,treatment.comment or "unknown") end nofskipped = nofskipped + 1 elseif done[name] then -- already done (avoid otf afm clash) if trace_names then report_names("%s font %a already done",suffix,completename) end nofduplicates = nofduplicates + 1 nofskipped = nofskipped + 1 elseif not exists(completename) then -- weird error if trace_names then report_names("%s font %a does not really exist",suffix,completename) end nofskipped = nofskipped + 1 elseif not is_qualified_path(completename) and findfile(completename,suffix) == "" then -- not locatable by backend anyway if trace_names then report_names("%s font %a cannot be found by backend",suffix,completename) end nofskipped = nofskipped + 1 else if #skip_paths > 0 then for i=1,#skip_paths do if find(pathpart,skip_paths[i]) then if trace_names then report_names("rejecting path of %s font %a",suffix,completename) end nofskipped = nofskipped + 1 return end end end if #skip_names > 0 then for i=1,#skip_paths do if find(basepart,skip_names[i]) then done[name] = true if trace_names then report_names("rejecting name of %s font %a",suffix,completename) end nofskipped = nofskipped + 1 return end end end if trace_names then report_names("identifying %s font %a",suffix,completename) end local result = nil local modification = lfs.attributes(completename,"modification") if olddata and modification and modification > 0 then local oldindex = oldindices[storedname] -- index into specifications if oldindex then local oldspecification = oldspecifications[oldindex] if oldspecification and oldspecification.filename == storedname then -- double check for out of sync local oldmodification = oldspecification.modification if oldmodification == modification then result = oldspecification specifications[#specifications + 1] = result else end else end elseif oldrejected[storedname] == modification then result = false end end if result == nil then local lsuffix = lower(suffix) local result, message = filters[lsuffix](completename) if result then if #result > 0 then for r=1,#result do local ok = check_name(data,result[r],storedname,modification,suffix,r-1) -- subfonts start at zero -- if not ok then -- nofskipped = nofskipped + 1 -- end end else local ok = check_name(data,result,storedname,modification,suffix) -- if not ok then -- nofskipped = nofskipped + 1 -- end end if trace_warnings and message and message ~= "" then report_names("warning when identifying %s font %a, %s",suffix,completename,message) end elseif trace_warnings then nofskipped = nofskipped + 1 report_names("error when identifying %s font %a, %s",suffix,completename,message or "unknown") end end done[name] = true end logs.flush() -- a bit overkill for each font, maybe not needed here end local function traverse(what, method) local list = filters.list for n=1,#list do local suffix = list[n] local t = os.gettimeofday() -- use elapser nofread, nofskipped, nofduplicates = 0, 0, 0 suffix = lower(suffix) report_names("identifying %s font files with suffix %a",what,suffix) method(suffix) suffix = upper(suffix) report_names("identifying %s font files with suffix %a",what,suffix) method(suffix) totalnofread, totalnofskipped, totalnofduplicates = totalnofread + nofread, totalnofskipped + nofskipped, totalnofduplicates + nofduplicates local elapsed = os.gettimeofday() - t report_names("%s %s files identified, %s skipped, %s duplicates, %s hash entries added, runtime %0.3f seconds",nofread,what,nofskipped,nofduplicates,nofread-nofskipped,elapsed) end logs.flush() end -- problem .. this will not take care of duplicates local function withtree(suffix) resolvers.dowithfilesintree(".*%." .. suffix .. "$", function(method,root,path,name) if method == "file" or method == "tree" then local completename = root .."/" .. path .. "/" .. name completename = resolveprefix(completename) -- no shortcut identify(completename,name,suffix,name) return true end end, function(blobtype,blobpath,pattern) blobpath = resolveprefix(blobpath) -- no shortcut report_names("scanning path %a for %s files",blobpath,suffix) end, function(blobtype,blobpath,pattern,total,checked,done) blobpath = resolveprefix(blobpath) -- no shortcut report_names("%s entries found, %s %s files checked, %s okay",total,checked,suffix,done) end) end local function withlsr(suffix) -- all trees -- we do this only for a stupid names run, not used for context itself, -- using the vars is too clumsy so we just stick to a full scan instead local pathlist = resolvers.splitpath(resolvers.showpath("ls-R") or "") walk_tree(pathlist,suffix,identify) end local function withsystem(suffix) -- OSFONTDIR cum suis walk_tree(names.getpaths(trace),suffix,identify) end traverse("tree",withtree) -- TEXTREE only if not usesystemfonts then report_names("ignoring system fonts") elseif texconfig.kpse_init then traverse("lsr", withlsr) else traverse("system", withsystem) end data.statistics.readfiles = totalnofread data.statistics.skippedfiles = totalnofskipped data.statistics.duplicatefiles = totalnofduplicates end local function addfilenames() local data = names.data local specifications = data.specifications local indices = { } local files = { } for i=1,#specifications do local fullname = specifications[i].filename files[cleanfilename(fullname)] = fullname indices[fullname] = i end data.files = files data.indices = indices end local function rejectclashes() -- just to be sure, so no explicit afm will be found then local specifications = names.data.specifications local used = { } local okay = { } local rejected = { } -- only keep modification local o = 0 for i=1,#specifications do local s = specifications[i] local f = s.fontname if f then local fnd = used[f] local fnm = s.filename if fnd then if trace_warnings then report_names("fontname %a clashes, %a rejected in favor of %a",f,fnm,fnd) end rejected[f] = s.modification else used[f] = fnm o = o + 1 okay[o] = s end else o = o + 1 okay[o] = s end end local d = #specifications - #okay if d > 0 then report_names("%s files rejected due to clashes",d) end names.data.specifications = okay names.data.rejected = rejected end local function resetdata() local mappings = { } local fallbacks = { } for _, k in next, filters.list do mappings [k] = { } fallbacks[k] = { } end names.data = { version = names.version, mappings = mappings, fallbacks = fallbacks, specifications = { }, families = { }, statistics = { }, names = { }, indices = { }, rejected = { }, datastate = resolvers.datastate(), } end function names.identify(force) local starttime = os.gettimeofday() -- use elapser resetdata() analyzefiles(not force and names.readdata(names.basename)) rejectclashes() collectfamilies() -- collectstatistics() cleanupkeywords() collecthashes() checkduplicates() addfilenames() -- sorthashes() -- will be resorted when saved collectstatistics() report_names("total scan time %0.3f seconds",os.gettimeofday()-starttime) end function names.is_permitted(name) return containers.is_usable(names.cache, name) end function names.writedata(name,data) containers.write(names.cache,name,data) end function names.readdata(name) return containers.read(names.cache,name) end function names.load(reload,force) if not names.loaded then if reload then if names.is_permitted(names.basename) then names.identify(force) names.writedata(names.basename,names.data) else report_names("unable to access database cache") end names.saved = true end local data = names.readdata(names.basename) names.data = data if not names.saved then if not data or not next(data) or not data.specifications or not next(data.specifications) then names.load(true) end names.saved = true end if not data then report_names("accessing the data table failed") else unpackreferences() sorthashes() end names.loaded = true end end local function list_them(mapping,sorted,pattern,t,all) if mapping[pattern] then t[pattern] = mapping[pattern] else for k=1,#sorted do local v = sorted[k] if not t[v] and find(v,pattern) then t[v] = mapping[v] if not all then return end end end end end function names.list(pattern,reload,all) -- here? names.load() -- todo reload if names.loaded then local t = { } local data = names.data if data then local list = filters.list local mappings = data.mappings local sorted_mappings = data.sorted_mappings local fallbacks = data.fallbacks local sorted_fallbacks = data.sorted_fallbacks for i=1,#list do local format = list[i] list_them(mappings[format],sorted_mappings[format],pattern,t,all) if next(t) and not all then return t end list_them(fallbacks[format],sorted_fallbacks[format],pattern,t,all) if next(t) and not all then return t end end end return t end end local reloaded = false local function is_reloaded() if not reloaded then local data = names.data if autoreload then local c_status = serialize(resolvers.datastate()) local f_status = serialize(data.datastate) if c_status == f_status then if trace_names then report_names("font database has matching configuration and file hashes") end return else report_names("font database has mismatching configuration and file hashes") end else report_names("font database is regenerated (controlled by directive 'fonts.autoreload')") end names.loaded = false reloaded = true logs.flush() names.load(true) end end --[[ldx--The resolver also checks if the cached names are loaded. Being clever here is for testing purposes only (it deals with names prefixed by an encoding name).
--ldx]]-- local function fuzzy(mapping,sorted,name,sub) local condensed = gsub(name,"[^%a%d]","") for k=1,#sorted do local v = sorted[k] if find(v,condensed) then return mapping[v], v end end end -- we could cache a lookup .. maybe some day ... (only when auto loaded!) local function foundname(name,sub) -- sub is not used currently local data = names.data local mappings = data.mappings local sorted_mappings = data.sorted_mappings local fallbacks = data.fallbacks local sorted_fallbacks = data.sorted_fallbacks local list = filters.list -- dilemma: we lookup in the order otf ttf ttc ... afm but now an otf fallback -- can come after an afm match ... well, one should provide nice names anyway -- and having two lists is not an option for i=1,#list do local l = list[i] local found = mappings[l][name] if found then if trace_names then report_names("resolved via direct name match: %a",name) end return found end end for i=1,#list do local l = list[i] local found, fname = fuzzy(mappings[l],sorted_mappings[l],name,sub) if found then if trace_names then report_names("resolved via fuzzy name match: %a onto %a",name,fname) end return found end end for i=1,#list do local l = list[i] local found = fallbacks[l][name] if found then if trace_names then report_names("resolved via direct fallback match: %a",name) end return found end end for i=1,#list do local l = list[i] local found, fname = fuzzy(sorted_mappings[l],sorted_fallbacks[l],name,sub) if found then if trace_names then report_names("resolved via fuzzy fallback match: %a onto %a",name,fname) end return found end end if trace_names then report_names("font with name %a cannot be found",name) end end function names.resolvedspecification(askedname,sub) if askedname and askedname ~= "" and names.enabled then askedname = cleanname(askedname) names.load() local found = foundname(askedname,sub) if not found and is_reloaded() then found = foundname(askedname,sub) end return found end end function names.resolve(askedname,sub) local found = names.resolvedspecification(askedname,sub) if found then return found.filename, found.subfont and found.rawname end end -- function names.getfilename(askedname,suffix) -- last resort, strip funny chars -- names.load() -- local files = names.data.files -- askedname = files and files[cleanfilename(askedname,suffix)] or "" -- if askedname == "" then -- return "" -- else -- never entered -- return resolvers.findbinfile(askedname,suffix) or "" -- end -- end function names.getfilename(askedname,suffix) -- last resort, strip funny chars names.load() local files = names.data.files local cleanname = cleanfilename(askedname,suffix) local found = files and files[cleanname] or "" if found == "" and is_reloaded() then files = names.data.files found = files and files[cleanname] or "" end if found and found ~= "" then return resolvers.findbinfile(found,suffix) or "" -- we still need to locate it end end -- specified search local function s_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,family) if family then for i=1,#family do local f = family[i] if f and weight == f.weight and style == f.style and width == f.width and variant == f.variant then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and weight == f.weight and style == f.style and width == f.width and variant == f.variant and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function s_collect_weight_style_width(found,done,all,weight,style,width,family) if family then for i=1,#family do local f = family[i] if f and weight == f.weight and style == f.style and width == f.width then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect_weight_style_width(found,done,all,weight,style,width,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and weight == f.weight and style == f.style and width == f.width and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function s_collect_weight_style(found,done,all,weight,style,family) if family then for i=1,#family do local f = family[i] if f and weight == f.weight and style == f.style then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect_weight_style(found,done,all,weight,style,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and weight == f.weight and style == f.style and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function s_collect_style_width(found,done,all,style,width,family) if family then for i=1,#family do local f = family[i] if f and style == f.style and width == f.width then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect_style_width(found,done,all,style,width,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and style == f.style and width == f.width and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function s_collect_weight(found,done,all,weight,family) if family then for i=1,#family do local f = family[i] if f and weight == f.weight then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect_weight(found,done,all,weight,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and weight == f.weight and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function s_collect_style(found,done,all,style,family) if family then for i=1,#family do local f = family[i] if f and style == f.style then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect_style(found,done,all,style,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and style == f.style and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function s_collect_width(found,done,all,width,family) if family then for i=1,#family do local f = family[i] if f and width == f.width then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect_width(found,done,all,width,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and width == f.width and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function s_collect(found,done,all,family) if family then for i=1,#family do local f = family[i] if f then found[#found+1], done[f] = f, true if not all then return end end end end end local function m_collect(found,done,all,families,sorted,strictname) for i=1,#sorted do local k = sorted[i] local family = families[k] for i=1,#family do local f = family[i] if not done[f] and find(f.fontname,strictname) then found[#found+1], done[f] = f, true if not all then return end end end end end local function collect(stage,found,done,name,weight,style,width,variant,all) local data = names.data local families = data.families local sorted = data.sorted_families local strictname = "^".. name -- to be checked local family = families[name] if trace_names then report_names("resolving name %a, weight %a, style %a, width %a, variant %a",name,weight,style,width,variant) end if weight and weight ~= "" then if style and style ~= "" then if width and width ~= "" then if variant and variant ~= "" then if trace_names then report_names("resolving stage %s, name %a, weight %a, style %a, width %a, variant %a",stage,name,weight,style,width,variant) end s_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,family) m_collect_weight_style_width_variant(found,done,all,weight,style,width,variant,families,sorted,strictname) else if trace_names then report_names("resolving stage %s, name %a, weight %a, style %a, width %a",stage,name,weight,style,width) end s_collect_weight_style_width(found,done,all,weight,style,width,family) m_collect_weight_style_width(found,done,all,weight,style,width,families,sorted,strictname) end else if trace_names then report_names("resolving stage %s, name %a, weight %a, style %a",stage,name,weight,style) end s_collect_weight_style(found,done,all,weight,style,family) m_collect_weight_style(found,done,all,weight,style,families,sorted,strictname) end else if trace_names then report_names("resolving stage %s, name %a, weight %a",stage,name,weight) end s_collect_weight(found,done,all,weight,family) m_collect_weight(found,done,all,weight,families,sorted,strictname) end elseif style and style ~= "" then if width and width ~= "" then if trace_names then report_names("resolving stage %s, name %a, style %a, width %a",stage,name,style,width) end s_collect_style_width(found,done,all,style,width,family) m_collect_style_width(found,done,all,style,width,families,sorted,strictname) else if trace_names then report_names("resolving stage %s, name %a, style %a",stage,name,style) end s_collect_style(found,done,all,style,family) m_collect_style(found,done,all,style,families,sorted,strictname) end elseif width and width ~= "" then if trace_names then report_names("resolving stage %s, name %a, width %a",stage,name,width) end s_collect_width(found,done,all,width,family) m_collect_width(found,done,all,width,families,sorted,strictname) else if trace_names then report_names("resolving stage %s, name %a",stage,name) end s_collect(found,done,all,family) m_collect(found,done,all,families,sorted,strictname) end end local function heuristic(name,weight,style,width,variant,all) -- todo: fallbacks local found, done = { }, { } --~ print(name,weight,style,width,variant) weight, style, width, variant = weight or "normal", style or "normal", width or "normal", variant or "normal" name = cleanname(name) collect(1,found,done,name,weight,style,width,variant,all) -- still needed ? if #found == 0 and variant ~= "normal" then -- not weight variant = "normal" collect(4,found,done,name,weight,style,width,variant,all) end if #found == 0 and width ~= "normal" then width = "normal" collect(2,found,done,name,weight,style,width,variant,all) end if #found == 0 and weight ~= "normal" then -- not style weight = "normal" collect(3,found,done,name,weight,style,width,variant,all) end if #found == 0 and style ~= "normal" then -- not weight style = "normal" collect(4,found,done,name,weight,style,width,variant,all) end -- local nf = #found if trace_names then if nf then local t = { } for i=1,nf do t[i] = formatters["%a"](found[i].fontname) end report_names("name %a resolved to %s instances: % t",name,nf,t) else report_names("name %a unresolved",name) end end if all then return nf > 0 and found else return found[1] end end function names.specification(askedname,weight,style,width,variant,reload,all) if askedname and askedname ~= "" and names.enabled then askedname = cleanname(askedname) -- or cleanname names.load(reload) local found = heuristic(askedname,weight,style,width,variant,all) if not found and is_reloaded() then found = heuristic(askedname,weight,style,width,variant,all) if not filename then found = foundname(askedname) -- old method end end return found end end function names.collect(askedname,weight,style,width,variant,reload,all) if askedname and askedname ~= "" and names.enabled then askedname = cleanname(askedname) -- or cleanname names.load(reload) local list = heuristic(askedname,weight,style,width,variant,true) if not list or #list == 0 and is_reloaded() then list = heuristic(askedname,weight,style,width,variant,true) end return list end end function names.collectspec(askedname,reload,all) local name, weight, style, width, variant = names.splitspec(askedname) return names.collect(name,weight,style,width,variant,reload,all) end function names.resolvespec(askedname,sub) -- redefined later local found = names.specification(names.splitspec(askedname)) if found then return found.filename, found.subfont and found.rawname end end function names.collectfiles(askedname,reload) -- no all if askedname and askedname ~= "" and names.enabled then askedname = cleanname(askedname) -- or cleanname names.load(reload) local list = { } local specifications = names.data.specifications for i=1,#specifications do local s = specifications[i] if find(cleanname(basename(s.filename)),askedname) then list[#list+1] = s end end return list end end -- todo: -- -- blacklisted = { -- ["cmr10.ttf"] = "completely messed up", -- } function names.exists(name) local found = false local list = filters.list for k=1,#list do local v = list[k] found = (findfile(name,v) or "") ~= "" if found then return found end end return (findfile(name,"tfm") or "") ~= "" or (names.resolve(name) or "") ~= "" end local lastlookups, lastpattern = { }, "" -- function names.lookup(pattern,name,reload) -- todo: find -- if lastpattern ~= pattern then -- names.load(reload) -- local specifications = names.data.specifications -- local families = names.data.families -- local lookups = specifications -- if name then -- lookups = families[name] -- elseif not find(pattern,"=",1,true) then -- lookups = families[pattern] -- end -- if trace_names then -- report_names("starting with %s lookups for %a",#lookups,pattern) -- end -- if lookups then -- for key, value in gmatch(pattern,"([^=,]+)=([^=,]+)") do -- local t, n = { }, 0 -- if find(value,"*",1,true) then -- value = topattern(value) -- for i=1,#lookups do -- local s = lookups[i] -- if find(s[key],value) then -- n = n + 1 -- t[n] = lookups[i] -- end -- end -- else -- for i=1,#lookups do -- local s = lookups[i] -- if s[key] == value then -- n = n + 1 -- t[n] = lookups[i] -- end -- end -- end -- if trace_names then -- report_names("%s matches for key %a with value %a",#t,key,value) -- end -- lookups = t -- end -- end -- lastpattern = pattern -- lastlookups = lookups or { } -- end -- return #lastlookups -- end local function look_them_up(lookups,specification) for key, value in next, specification do local t, n = { }, 0 if find(value,"*",1,true) then value = topattern(value) for i=1,#lookups do local s = lookups[i] if find(s[key],value) then n = n + 1 t[n] = lookups[i] end end else for i=1,#lookups do local s = lookups[i] if s[key] == value then n = n + 1 t[n] = lookups[i] end end end if trace_names then report_names("%s matches for key %a with value %a",#t,key,value) end lookups = t end return lookups end local function first_look(name,reload) names.load(reload) local data = names.data local specifications = data.specifications local families = data.families if name then return families[name] else return specifications end end function names.lookup(pattern,name,reload) -- todo: find names.load(reload) local data = names.data local specifications = data.specifications local families = data.families local lookups = specifications if name then name = cleanname(name) end if type(pattern) == "table" then local familyname = pattern.familyname if familyname then familyname = cleanname(familyname) pattern.familyname = familyname end local lookups = first_look(name or familyname,reload) if lookups then if trace_names then report_names("starting with %s lookups for '%T'",#lookups,pattern) end lookups = look_them_up(lookups,pattern) end lastpattern = false lastlookups = lookups or { } elseif lastpattern ~= pattern then local lookups = first_look(name or (not find(pattern,"=",1,true) and pattern),reload) if lookups then if trace_names then report_names("starting with %s lookups for %a",#lookups,pattern) end local specification = settings_to_hash(pattern) local familyname = specification.familyname if familyname then familyname = cleanname(familyname) specification.familyname = familyname end lookups = look_them_up(lookups,specification) end lastpattern = pattern lastlookups = lookups or { } end return #lastlookups end function names.getlookupkey(key,n) local l = lastlookups[n or 1] return (l and l[key]) or "" end function names.noflookups() return #lastlookups end function names.getlookups(pattern,name,reload) if pattern then names.lookup(pattern,name,reload) end return lastlookups end -- The following is new ... watch the overload! local specifications = allocate() names.specifications = specifications -- files = { -- name = "antykwapoltawskiego", -- list = { -- ["AntPoltLtCond-Regular.otf"] = { -- -- name = "antykwapoltawskiego", -- style = "regular", -- weight = "light", -- width = "condensed", -- }, -- }, -- } function names.register(files) if files then local list, commonname = files.list, files.name if list then local n, m = 0, 0 for filename, filespec in next, list do local name = lower(filespec.name or commonname) if name and name ~= "" then local style = normalized_styles [lower(filespec.style or "normal")] local width = normalized_widths [lower(filespec.width or "normal")] local weight = normalized_weights [lower(filespec.weight or "normal")] local variant = normalized_variants[lower(filespec.variant or "normal")] local weights = specifications[name ] if not weights then weights = { } specifications[name ] = weights end local styles = weights [weight] if not styles then styles = { } weights [weight] = styles end local widths = styles [style ] if not widths then widths = { } styles [style ] = widths end local variants = widths [width ] if not variants then variants = { } widths [width ] = variants end variants[variant] = filename n = n + 1 else m = m + 1 end end if trace_specifications then report_names("%s filenames registered, %s filenames rejected",n,m) end end end end function names.registered(name,weight,style,width,variant) local ok = specifications[name] ok = ok and (ok[(weight and weight ~= "" and weight ) or "normal"] or ok.normal) ok = ok and (ok[(style and style ~= "" and style ) or "normal"] or ok.normal) ok = ok and (ok[(width and width ~= "" and width ) or "normal"] or ok.normal) ok = ok and (ok[(variant and variant ~= "" and variant) or "normal"] or ok.normal) -- -- todo: same fallbacks as with database -- if ok then return { filename = ok, subname = "", -- rawname = nil, } end end function names.resolvespec(askedname,sub) -- overloads previous definition local name, weight, style, width, variant = names.splitspec(askedname) if trace_specifications then report_names("resolving specification: %a to name=%s, weight=%s, style=%s, width=%s, variant=%s",askedname,name,weight,style,width,variant) end local found = names.registered(name,weight,style,width,variant) if found and found.filename then if trace_specifications then report_names("resolved by registered names: %a to %s",askedname,found.filename) end return found.filename, found.subname, found.rawname else found = names.specification(name,weight,style,width,variant) if found and found.filename then if trace_specifications then report_names("resolved by font database: %a to %s",askedname,found.filename) end return found.filename, found.subfont and found.rawname end end if trace_specifications then report_names("unresolved: %s",askedname) end end -- We could generate typescripts with designsize info from the name database but -- it's not worth the trouble as font names remain a mess: for instance how do we -- idenfity a font? Names, families, subfamilies or whatever snippet can contain -- a number related to the design size and so we end up with fuzzy logic again. So, -- instead it's easier to make a few goody files. -- -- local hash = { } -- -- for i=1,#specifications do -- local s = specifications[i] -- local min = s.minsize or 0 -- local max = s.maxsize or 0 -- if min ~= 0 or max ~= 0 then -- -- the usual name mess: -- -- antykwa has modifiers so we need to take these into account, otherwise we get weird combinations -- -- ebgaramond has modifiers with the size encoded, so we need to strip this in order to recognized similar styles -- -- lm has 'slanted appended in some names so how to choose that one -- -- -- local modifier = string.gsub(s.modifiers or "normal","%d","") -- -- print funny modifier -- local instance = string.formatters["%s-%s-%s-%s-%s-%s"](s.familyname,s.width,s.style,s.weight,s.variant,modifier) -- local h = hash[instance] -- if not h then -- h = { } -- hash[instance] = h -- end -- size = string.formatters["%0.1fpt"]((min)/10) -- h[size] = s.filename -- end -- end -- -- local newhash = { } -- -- for k, v in next, hash do -- if next(v,next(v)) then -- -- local instance = string.match(k,"(.+)%-.+%-.+%-.+$") -- local instance = string.match(k,"(.+)%-.+%-.+$") -- local instance = string.gsub(instance,"%-normal$","") -- if not newhash[instance] then -- newhash[instance] = v -- end -- end -- end -- -- inspect(newhash)