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 | 579 |
1 files changed, 466 insertions, 113 deletions
diff --git a/Master/texmf-dist/tex/context/base/font-syn.lua b/Master/texmf-dist/tex/context/base/font-syn.lua index 27176dade05..18ed46a2f88 100644 --- a/Master/texmf-dist/tex/context/base/font-syn.lua +++ b/Master/texmf-dist/tex/context/base/font-syn.lua @@ -12,13 +12,14 @@ 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 = table.serialize +local serialize, sortedhash = table.serialize, table.sortedhash local lpegmatch = lpeg.match local unpack = unpack or table.unpack -local formatters = string.formatters +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 @@ -34,38 +35,45 @@ local findfile = resolvers.findfile local cleanpath = resolvers.cleanpath local resolveresolved = resolvers.resolve +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") +local report_names = logs.reporter("fonts","names") --[[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]]-- -fonts = fonts or { } -- also used elsewhere +fonts = fonts or { } -- also used elsewhere + +local names = font.names or allocate { } +fonts.names = names -local names = font.names or allocate { } -fonts.names = names +local filters = names.filters or { } +names.filters = filters -local filters = names.filters or { } -names.filters = filters +local treatments = fonts.treatments or { } +fonts.treatments = treatments -names.data = names.data or allocate { } +names.data = names.data or allocate { } -names.version = 1.110 -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) +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 autoreload = true +local usesystemfonts = true +local autoreload = true -directives.register("fonts.autoreload", function(v) autoreload = toboolean(v) end) +directives.register("fonts.autoreload", function(v) autoreload = toboolean(v) end) +directives.register("fonts.usesystemfonts", function(v) usesystemfonts = toboolean(v) end) --[[ldx-- <p>A few helpers.</p> @@ -73,7 +81,33 @@ directives.register("fonts.autoreload", function(v) autoreload = toboolean(v) en local P, C, Cc, Cs = lpeg.P, lpeg.C, lpeg.Cc, lpeg.Cs --- what to do with 'thin' +-- -- 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") @@ -82,6 +116,7 @@ local weights = Cs ( -- not extra + P("ultrabold") + P("extrabold") + P("ultralight") + + P("extralight") + P("bold") + P("demi") + P("semi") @@ -90,10 +125,22 @@ local weights = Cs ( -- not extra + P("heavy") + P("ultra") + P("black") - + P("bol") -- / "bold" +--+ 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", } @@ -105,8 +152,9 @@ local styles = Cs ( + P("oblique") / "italic" + P("slanted") + P("roman") / "normal" - + P("ital") / "italic" - + P("ita") / "italic" + + P("ital") / "italic" -- might be tricky + + P("ita") / "italic" -- might be tricky +--+ P("obli") / "oblique" ) local normalized_styles = sparse { @@ -120,6 +168,7 @@ local widths = Cs( + P("thin") + P("expanded") + P("cond") / "condensed" +--+ P("expa") / "expanded" + P("normal") + P("book") / "normal" ) @@ -178,6 +227,28 @@ names.knownvariants = { "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 @@ -232,19 +303,82 @@ filters.ttf = fontloader.info filters.ttc = fontloader.info filters.dfont = fontloader.info -function fontloader.fullinfo(...) -- check with taco what we get / could get +-- 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 + +-- function fontloader.fullinfo(...) -- check with taco what we get / could get +-- local ff = fontloader.open(...) +-- if ff then +-- local d = ff -- and fontloader.to_table(ff) +-- d.glyphs, d.subfonts, d.gpos, d.gsub, d.lookups = nil, nil, nil, nil, nil +-- fontloader.close(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. + +-- function fontloader.fullinfo(name) +-- local ff = fontloader.open(name) +-- if ff then +-- local fields = table.tohash(fontloader.fields(ff),true) +-- 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) +-- fontloader.close(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. + +function fontloader.fullinfo(...) local ff = fontloader.open(...) if ff then - local d = ff and fontloader.to_table(ff) - d.glyphs, d.subfonts, d.gpos, d.gsub, d.lookups = nil, nil, nil, nil, nil - fontloader.close(ff) + local d = { } -- ff is userdata so [1] or # fails on it + setmetatableindex(d,ff) return d else return nil, "error in loading font" end end +-- We don't get the design_* values here as for that the fontloader has to load feature +-- info and therefore we're not much better off than using 'open'. +-- +-- if tonumber(status.luatex_version) > 78 or (tonumber(status.luatex_version) == 78 and tonumber(status.luatex_revision) > 0) then +-- fontloader.fullinfo = fontloader.info +-- end + filters.otf = fontloader.fullinfo +filters.ttf = fontloader.fullinfo function filters.afm(name) -- we could parse the afm file as well, and then report an error but @@ -257,12 +391,12 @@ function filters.afm(name) local f = io.open(name) if f then local hash = { } - for line in f:lines() do + 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") then + if find(line,"StartCharMetrics",1,true) then break end end @@ -420,15 +554,17 @@ local function check_name(data,result,filename,modification,suffix,subfont) -- 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 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) @@ -458,28 +594,46 @@ local function check_name(data,result,filename,modification,suffix,subfont) if not familyname then familyname = a_name end - fontname = fontname or fullname or familyname or basename(filename) + 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 - 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, - minsize = result.design_range_bottom or 0, - maxsize = result.design_range_top or 0, - designsize = result.design_size or 0, - modification = modification or 0, + 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() @@ -502,10 +656,10 @@ local function cleanupkeywords() 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" - if not weight or weight == "" then weight = "normal" end - if not style or style == "" then style = "normal" end - if not width or width == "" then width = "normal" end - if not variant or variant == "" then variant = "normal" end + 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 @@ -524,12 +678,22 @@ local function collectstatistics() local data = names.data local specifications = data.specifications if specifications then - local weights = { } - local styles = { } - local widths = { } - local variants = { } + 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] + local s = specifications[i] + -- normal stuff local weight = s.weight local style = s.style local width = s.width @@ -538,13 +702,64 @@ local function collectstatistics() 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 - local stats = data.statistics - stats.weights = weights - stats.styles = styles - stats.widths = widths - stats.variants = variants - stats.fonts = #specifications + 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 @@ -608,8 +823,11 @@ local function checkduplicate(where) -- fails on "Romantik" but that's a border local specifications = data.specifications local loaded = { } if specifications and mapping then - for _, m in next, mapping do - for k, v in next, m do + -- 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] @@ -633,7 +851,7 @@ local function checkduplicate(where) -- fails on "Romantik" but that's a border end end local n = 0 - for k, v in table.sortedhash(loaded) do + for k, v in sortedhash(loaded) do local nv = #v if nv > 1 then if trace_warnings then @@ -720,7 +938,7 @@ local function analyzefiles(olddata) local oldindices = olddata and olddata.indices or { } local oldspecifications = olddata and olddata.specifications or { } local oldrejected = olddata and olddata.rejected or { } - local treatmentdata = fonts.treatments.data + local treatmentdata = treatments.data or { } -- when used outside context local function identify(completename,name,suffix,storedname) local pathpart, basepart = splitbase(completename) nofread = nofread + 1 @@ -796,9 +1014,10 @@ local function analyzefiles(olddata) end end if result == nil then - local result, message = filters[lower(suffix)](completename) + local lsuffix = lower(suffix) + local result, message = filters[lsuffix](completename) if result then - if result[1] 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 @@ -868,7 +1087,9 @@ local function analyzefiles(olddata) walk_tree(names.getpaths(trace),suffix,identify) end traverse("tree",withtree) -- TEXTREE only - if texconfig.kpse_init then + if not usesystemfonts then + report_names("ignoring system fonts") + elseif texconfig.kpse_init then traverse("lsr", withlsr) else traverse("system", withsystem) @@ -954,12 +1175,13 @@ function names.identify(force) analyzefiles(not force and names.readdata(names.basename)) rejectclashes() collectfamilies() - collectstatistics() + -- collectstatistics() cleanupkeywords() collecthashes() checkduplicates() addfilenames() -- sorthashes() -- will be resorted when saved + collectstatistics() report_names("total scan time %0.3f seconds",os.gettimeofday()-starttime) end @@ -1571,46 +1793,131 @@ 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,"=") then - lookups = families[pattern] +-- 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("starting with %s lookups for %a",#lookups,pattern) + 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 - for key, value in gmatch(pattern,"([^=,]+)=([^=,]+)") do - local t, n = { }, 0 - if find(value,"*") then - value = string.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 + 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 { } @@ -1722,3 +2029,49 @@ function names.resolvespec(askedname,sub) -- overloads previous definition 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) |