diff options
author | Philipp Gesang <phg42.2a@gmail.com> | 2013-04-29 15:02:52 +0200 |
---|---|---|
committer | Philipp Gesang <phg42.2a@gmail.com> | 2013-04-29 15:02:52 +0200 |
commit | 71f44e48c73c13d23431274f5886f9480a60fc24 (patch) | |
tree | 8939726a4ab60d86c2461f74827940e8fb0e3e27 | |
parent | 9c16f2f73eed3946020b0197d9fe97034c4f9f24 (diff) | |
parent | e79f34d859d48be485589f19fc6905afa5872a53 (diff) | |
download | luaotfload-71f44e48c73c13d23431274f5886f9480a60fc24.tar.gz |
merge newsyntax branch into master
-rwxr-xr-x | fontdbutil.lua | 14 | ||||
-rw-r--r-- | luaotfload-database.lua | 185 | ||||
-rw-r--r-- | luaotfload-features.lua | 395 | ||||
-rw-r--r-- | luaotfload.dtx | 373 | ||||
-rw-r--r-- | tests/pln-request-4-slashed.tex | 12 | ||||
-rw-r--r-- | tests/pln-request-5-cached.tex | 18 | ||||
-rw-r--r-- | tests/pln-subfont-1.tex | 12 | ||||
-rw-r--r-- | tests/pln-tfm.tex | 8 |
8 files changed, 787 insertions, 230 deletions
diff --git a/fontdbutil.lua b/fontdbutil.lua index 1aa99d0..470d282 100755 --- a/fontdbutil.lua +++ b/fontdbutil.lua @@ -28,7 +28,7 @@ string.quoted = string.quoted or function (str) return string.format("%q",str) end -dofile(loader_path) +require(loader_path) --[[doc-- Depending on how the script is called we change its behavior. @@ -276,14 +276,20 @@ actions.query = function (job) optsize = 0, } - local foundname, _whatever, success = + local foundname, subfont, success = fonts.names.resolve(nil, nil, tmpspec) if success then logs.names_report(false, 1, "resolve", "Font “%s” found!", query) - logs.names_report(false, 1, - "resolve", "Resolved file name “%s”", foundname) + if subfont then + logs.names_report(false, 1, "resolve", + "Resolved file name “%s”, subfont nr. “%s”", + foundname, subfont) + else + logs.names_report(false, 1, + "resolve", "Resolved file name “%s”", foundname) + end if job.show_info then show_font_info(foundname) end diff --git a/luaotfload-database.lua b/luaotfload-database.lua index 3085b63..d4613ba 100644 --- a/luaotfload-database.lua +++ b/luaotfload-database.lua @@ -239,6 +239,8 @@ local load_lua_file = function (path) end --- define locals in scope +local crude_file_lookup +local crude_file_lookup_verbose local find_closest local flush_cache local font_fullinfo @@ -246,6 +248,7 @@ local load_names local read_fonts_conf local reload_db local resolve +local resolve_cached local save_names local scan_external_dir local update_names @@ -298,9 +301,13 @@ do end end ---- chain: barenames -> [fullnames ->] basenames -> findfile -local crude_file_lookup_verbose = function (data, filename) - local mappings = data.mappings +local type1_formats = { "tfm", "ofm", } + +--- string -> (string * bool | int) +crude_file_lookup_verbose = function (filename) + if not names.data then names.data = load_names() end + local data = names.data + local mappings = data.mappings local found --- look up in db first ... @@ -323,29 +330,26 @@ local crude_file_lookup_verbose = function (data, filename) if found and mappings[found] then found = mappings[found].filename report("info", 0, "db", - "crude file lookup: req=%s; hit=bare; ret=%s", + "crude file lookup: req=%s; hit=base; ret=%s", filename, found[1]) return found end - --- now look for tfm et al.; will be superseded by proper - --- format lookup - found = resolvers.findfile(filename, "tfm") - if found then - report("info", 0, "db", - "crude file lookup: req=tfm; hit=bare; ret=%s", found) - return { found, false } - end - found = resolvers.findfile(filename, "ofm") - if found then - report("info", 0, "db", - "crude file lookup: req=ofm; hit=bare; ret=%s", found) - return { found, false } + --- ofm and tfm + for i=1, #type1_formats do + local format = type1_formats[i] + if resolvers.findfile(filename, format) then + return { file.addsuffix(filename, format), false }, format + end end - return false + return { filename, false }, nil end -local crude_file_lookup = function (data, filename) +--- string -> (string * bool | int) +crude_file_lookup = function (filename) + if not names.data then names.data = load_names() end + local data = names.data + local mappings = data.mappings local found = data.barenames[filename] -- or data.fullnames[filename] or data.basenames[filename] @@ -353,11 +357,13 @@ local crude_file_lookup = function (data, filename) found = data.mappings[found] if found then return found.filename end end - found = resolvers.findfile(filename, "tfm") - if found then return { found, false } end - found = resolvers.findfile(filename, "ofm") - if found then return { found, false } end - return false + for i=1, #type1_formats do + local format = type1_formats[i] + if resolvers.findfile(filename, format) then + return { file.addsuffix(filename, format), false }, format + end + end + return { filename, false }, nil end --[[doc-- @@ -383,22 +389,6 @@ TODO: 9) ??? n) PROFIT!!! ---doc]]-- - ---- the resolver is called after the font request is parsed ---- this is where we insert the cache -local normal_resolver = fonts.definers.resolve -local dummy_resolver = function (specification) - --- this ensures that the db is always loaded - --- before a lookup occurs - if not names.data then names.data = load_names() end - --inspect(specification) - local resolved = normal_resolver(specification) - --inspect(resolved) - return resolved -end - ---[[doc-- The name lookup requires both the “name” and some other keys, so we’ll concatenate them. The spec is modified in place (ugh), so we’ll have to catalogue what @@ -431,34 +421,30 @@ We’ll just cache a deep copy of the entire spec as it leaves the resolver, lest we want to worry if we caught all the details. --doc]]-- ---- spec -> spec -local cached_resolver = function (specification) +--- 'a -> 'a -> table -> (string * int|boolean * boolean) +resolve_cached = function (_, _, specification) if not names.data then names.data = load_names() end local request_cache = names.data.request_cache local request = specification.specification - report("info", 4, "cache", - "looking for “%s” in cache ...", + report("log", 4, "cache", "looking for “%s” in cache ...", request) + local found = names.data.request_cache[request] + + --- case 1) cache positive ---------------------------------------- if found then --- replay fields from cache hit report("info", 4, "cache", "found!") - for i=1, #cache_fields do - local f = cache_fields[i] - if found[f] then specification[f] = found[f] end - end - return specification + return found[1], found[2], true end - report("info", 4, "cache", "not cached; resolving") + report("log", 4, "cache", "not cached; resolving") + --- case 2) cache negative ---------------------------------------- --- first we resolve normally ... - local resolved_spec = normal_resolver(specification) - --- ... then we add the fields to the cache - local entry = { } - for i=1, #cache_fields do - local f = cache_fields[i] - entry[f] = resolved_spec[f] - end - report("info", 4, "cache", "new entry: %s", request) + local filename, subfont, success = resolve(nil, nil, specification) + if not success then return filename, subfont, false end + --- ... then we add the fields to the cache ... ... + local entry = { filename, subfont } + report("log", 4, "cache", "new entry: %s", request) names.data.request_cache[request] = entry --- obviously, the updated cache needs to be stored. @@ -466,20 +452,13 @@ local cached_resolver = function (specification) --- whenever the cache is updated. --- TODO this should trigger a save only once the --- document is compiled (finish_pdffile callback?) - report("info", 5, "cache", "saving updated cache") + --- TODO we should speed up writing by separating + --- the cache from the db + report("log", 5, "cache", "saving updated cache") save_names() - return resolved_spec + return filename, subfont, true end -local resolvers = { - dummy = dummy_resolver, - normal = normal_resolver, - cached = cached_resolver, -} - -fonts.definers.resolve = resolvers[config.luaotfload.resolver] ---fonts.definers.resolve = resolvers.cached - --[[doc-- Luatex-fonts, the font-loader package luaotfload imports, comes with @@ -522,12 +501,6 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con if not fonts_loaded then names.data = load_names() end local data = names.data - if specification.lookup == "file" then - local found = crude_file_lookup(data, specification.name) - --local found = crude_file_lookup_verbose(data, specification.name) - if found then return found[1], found[2], true end - end - local name = sanitize_string(specification.name) local style = sanitize_string(specification.style) or "regular" @@ -570,8 +543,6 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con local found = { } local synonym_set = style_synonyms.set for _,face in next, data.mappings do - --- TODO we really should store those in dedicated - --- .sanitized field local family, subfamily, fullname, psname, fontname, pfullname local facenames = face.sanitized @@ -648,12 +619,14 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con end if #found == 1 then - if kpselookup(found[1].filename[1]) then + --- “found” is really synonymous with “registered in the db”. + local filename = found[1].filename[1] + if lfsisfile(filename) or kpselookup(filename) then report("log", 0, "resolve", "font family='%s', subfamily='%s' found: %s", - name, style, found[1].filename[1] + name, style, filename ) - return found[1].filename[1], found[1].filename[2], true + return filename, found[1].filename[2], true end elseif #found > 1 then -- we found matching font(s) but not in the requested optical @@ -669,22 +642,25 @@ resolve = function (_,_,specification) -- the 1st two parameters are used by Con least = difference end end - if kpselookup(closest.filename[1]) then + local filename = closest.filename[1] + if lfsisfile(filename) or kpselookup(filename) then report("log", 0, "resolve", "font family='%s', subfamily='%s' found: %s", - name, style, closest.filename[1] + name, style, filename ) - return closest.filename[1], closest.filename[2], true + return filename, closest.filename[2], true end elseif found.fallback then - return found.fallback.filename[1], found.fallback.filename[2], true + return found.fallback.filename[1], + found.fallback.filename[2], + true end --- no font found so far if not fonts_reloaded then --- last straw: try reloading the database return reload_db( - "unresoled font name: “" .. name .. "”", + "unresolved font name: “" .. name .. "”", resolve, nil, nil, specification ) end @@ -741,9 +717,7 @@ find_closest = function (name, limit) local name = sanitize_string(name) limit = limit or fuzzy_limit - if not fonts_loaded then - names.data = load_names() - end + if not fonts_loaded then names.data = load_names() end local data = names.data @@ -870,10 +844,10 @@ font_fullinfo = function (filename, subfont, texmf) tfmdata.fontname = metadata.fontname tfmdata.fullname = metadata.fullname tfmdata.familyname = metadata.familyname - tfmdata.filename = { - texmf and filebasename(filename) or filename, - subfont - } + if texmf then + filename = filebasename(filename) + end + tfmdata.filename = { filename, subfont } tfmdata.weight = metadata.pfminfo.weight tfmdata.width = metadata.pfminfo.width tfmdata.slant = metadata.italicangle @@ -1508,15 +1482,24 @@ scan_external_dir = function (dir) end --- export functionality to the namespace “fonts.names” -names.flush_cache = flush_cache -names.load = load_names -names.save = save_names -names.scan = scan_external_dir -names.update = update_names - -names.resolve = resolve --- replace the resolver from luatex-fonts -names.resolvespec = resolve -names.find_closest = find_closest +names.flush_cache = flush_cache +names.load = load_names +names.save = save_names +names.scan = scan_external_dir +names.update = update_names +names.crude_file_lookup = crude_file_lookup +names.crude_file_lookup_verbose = crude_file_lookup_verbose + +--- replace the resolver from luatex-fonts +if config.luaotfload.resolver == "cached" then + report("info", 0, "cache", "caching of name: lookups active") + names.resolve = resolve_cached + names.resolvespec = resolve_cached +else + names.resolve = resolve + names.resolvespec = resolve +end +names.find_closest = find_closest -- for testing purpose names.read_fonts_conf = read_fonts_conf diff --git a/luaotfload-features.lua b/luaotfload-features.lua index 78f8256..6cbfdf4 100644 --- a/luaotfload-features.lua +++ b/luaotfload-features.lua @@ -26,7 +26,7 @@ function fonts.definers.getspecification(str) return "", str, "", ":", str end -local feature_list = { } +local old_feature_list = { } local report = logs.names_report @@ -64,20 +64,23 @@ local isstyle = function (request) for _,v in next, request do local stylename = supported[v] if stylename then - feature_list.style = stylename + old_feature_list.style = stylename elseif stringfind(v, "^s=") then --- after all, we want everything after the second byte ... local val = stringsub(v, 3) - feature_list.optsize = val + old_feature_list.optsize = val elseif stylename == false then report("log", 0, - "load font", "unsupported font option: %s", v) + "load", "unsupported font option: %s", v) elseif not stringis_empty(v) then - feature_list.style = stringgsub(v, "[^%a%d]", "") + old_feature_list.style = stringgsub(v, "[^%a%d]", "") end end end +--- TODO an option to dump the default features for a script would make +--- a nice addition to luaotfload-tool + local defaults = { dflt = { "ccmp", "locl", "rlig", "liga", "clig", @@ -131,17 +134,22 @@ defaults.tibt = defaults.khmr defaults.lao = defaults.thai +--[[doc-- +Which features are active by default depends on the script requested. +--doc]]-- + --- (string, string) dict -> (string, string) dict local set_default_features = function (speclist) + speclist = speclist or { } local script = speclist.script or "dflt" - report("log", 0, "load font", + report("log", 0, "load", "auto-selecting default features for script: %s", script) local requested = defaults[script] if not requested then - report("log", 0, "load font", + report("log", 0, "load", "no defaults for script “%s”, falling back to “dflt”", script) requested = defaults.dflt @@ -149,9 +157,7 @@ local set_default_features = function (speclist) for i=1, #requested do local feat = requested[i] - if speclist[feat] ~= false then - speclist[feat] = true - end + if speclist[feat] ~= false then speclist[feat] = true end end for feat, state in next, global_defaults do @@ -163,14 +169,15 @@ local set_default_features = function (speclist) return speclist end -local function issome () feature_list.lookup = 'name' end -local function isfile () feature_list.lookup = 'file' end -local function isname () feature_list.lookup = 'name' end -local function thename(s) feature_list.name = s end -local function issub (v) feature_list.sub = v end -local function istrue (s) feature_list[s] = true end -local function isfalse(s) feature_list[s] = false end -local function iskey (k,v) feature_list[k] = v end +-- --[==[obsolete-- +local function issome () old_feature_list.lookup = 'name' end +local function isfile () old_feature_list.lookup = 'file' end +local function isname () old_feature_list.lookup = 'name' end +local function thename(s) old_feature_list.name = s end +local function issub (v) old_feature_list.sub = v end +local function istrue (s) old_feature_list[s] = true end +local function isfalse(s) old_feature_list[s] = false end +local function iskey (k,v) old_feature_list[k] = v end local P, S, R, C = lpeg.P, lpeg.S, lpeg.R, lpeg.C @@ -180,7 +187,8 @@ local spaces = P(" ")^0 local namespec = (1-S("/:("))^1 local filespec = (R("az", "AZ") * P(":"))^-1 * (1-S(":("))^1 local stylespec = spaces * P("/") * (((1-P(":"))^0)/isstyle) * spaces -local filename = (P("file:")/isfile * (filespec/thename)) + (P("[") * P(true)/isname * (((1-P("]"))^0)/thename) * P("]")) +local filename = (P("file:")/isfile * (filespec/thename)) + + (P("[") * P(true)/isname * (((1-P("]"))^0)/thename) * P("]")) local fontname = (P("name:")/isname * (namespec/thename)) + P(true)/issome * (namespec/thename) local sometext = (R("az","AZ","09") + S("+-.,"))^1 local truevalue = P("+") * spaces * (sometext/istrue) @@ -190,67 +198,318 @@ local somevalue = sometext/istrue local subvalue = P("(") * (C(P(1-S("()"))^1)/issub) * P(")") -- for Kim local option = spaces * (keyvalue + falsevalue + truevalue + somevalue) * spaces local options = P(":") * spaces * (P(";")^0 * option)^0 -local pattern = (filename + fontname) * subvalue^0 * stylespec^0 * options^0 - -local function colonized(specification) -- xetex mode - feature_list = { } - lpeg.match(pattern,specification.specification) - feature_list = set_default_features(feature_list) - if feature_list.style then - specification.style = feature_list.style - feature_list.style = nil +local oldsyntax = (filename + fontname) * subvalue^0 * stylespec^0 * options^0 + +--- to be annihilated +local function old_behavior (specification) -- xetex mode + old_feature_list = { } + lpeg.match(oldsyntax,specification.specification) + old_feature_list = set_default_features(old_feature_list) + if old_feature_list.style then + specification.style = old_feature_list.style + old_feature_list.style = nil end - if feature_list.optsize then - specification.optsize = feature_list.optsize - feature_list.optsize = nil + if old_feature_list.optsize then + specification.optsize = old_feature_list.optsize + old_feature_list.optsize = nil end - if feature_list.name then - if resolvers.findfile(feature_list.name, "tfm") then - feature_list.lookup = "file" - feature_list.name = file.addsuffix(feature_list.name, "tfm") - elseif resolvers.findfile(feature_list.name, "ofm") then - feature_list.lookup = "file" - feature_list.name = file.addsuffix(feature_list.name, "ofm") + if old_feature_list.name then + if resolvers.findfile(old_feature_list.name, "tfm") then + old_feature_list.lookup = "file" + old_feature_list.name = file.addsuffix(old_feature_list.name, "tfm") + elseif resolvers.findfile(old_feature_list.name, "ofm") then + old_feature_list.lookup = "file" + old_feature_list.name = file.addsuffix(old_feature_list.name, "ofm") end - specification.name = feature_list.name - feature_list.name = nil + specification.name = old_feature_list.name + old_feature_list.name = nil end - if feature_list.lookup then - specification.lookup = feature_list.lookup - feature_list.lookup = nil + --- this test overwrites valid file: requests for xetex bracket + --- syntax + if old_feature_list.lookup then + specification.lookup = old_feature_list.lookup + old_feature_list.lookup = nil end - if feature_list.sub then - specification.sub = feature_list.sub - feature_list.sub = nil + if old_feature_list.sub then + specification.sub = old_feature_list.sub + old_feature_list.sub = nil end - if not feature_list.mode then + if not old_feature_list.mode then -- if no mode is set, use our default - feature_list.mode = fonts.mode + old_feature_list.mode = fonts.mode + end + specification.features.normal = fonts.handlers.otf.features.normalize(old_feature_list) + return specification +end + +--fonts.definers.registersplit(":",old_behavior,"cryptic") +--fonts.definers.registersplit("", old_behavior,"more cryptic") -- catches \font\text=[names] +--obsolete]==]-- + +----------------------------------------------------------------------- +--- request syntax parser 2.2 +----------------------------------------------------------------------- +--- the luaotfload font request syntax (see manual) +--- has a canonical form: +--- +--- \font<csname>=<prefix>:<identifier>:<features> +--- +--- where +--- <csname> is the control sequence that activates the font +--- <prefix> is either “file” or “name”, determining the lookup +--- <identifer> is either a file name (no path) or a font +--- name, depending on the lookup +--- <features> is a list of switches or options, separated by +--- semicolons or commas; a switch is of the form “+” foo +--- or “-” foo, options are of the form lhs “=” rhs +--- +--- however, to ensure backward compatibility we also have +--- support for Xetex-style requests. +--- +--- for the Xetex emulation see: +--- · The XeTeX Reference Guide by Will Robertson, 2011 +--- · The XeTeX Companion by Michel Goosens, 2010 +--- · About XeTeX by Jonathan Kew, 2005 +--- +--- +--- caueat emptor. +--- the request is parsed into one of **four** different +--- lookup categories: the regular ones, file and name, +--- as well as the Xetex compatibility ones, path and anon. +--- (maybe a better choice of identifier would be “ambig”.) +--- +--- according to my reconstruction, the correct chaining +--- of the lookups for each category is as follows: +--- +--- | File -> ( db/filename lookup; +--- db/basename lookup; +--- kpse.find_file() ) +--- | Name -> ( names.resolve() ) +--- | Path -> ( db/filename lookup; +--- db/basename lookup; +--- kpse.find_file(); +--- fullpath lookup ) +--- | Anon -> ( names.resolve(); (* most general *) +--- db/filename lookup; +--- db/basename lookup; +--- kpse.find_file(); +--- fullpath lookup ) +--- +--- the database should be generated only if the chain has +--- been completed, and then only once. +--- +--- caching of successful lookups is essential. we need +--- an additional subtable "cached" in the database. it +--- should be nil’able by issuing fontdbutil --flush or +--- something. if a cache miss is followed by a successful +--- lookup, then it will be counted as new addition to the +--- cache. we also need a config option to ignore caching. +--- +--- also everything has to be finished by tomorrow at noon. +--- +----------------------------------------------------------------------- + + +local stringlower = string.lower + +local toboolean = function (s) + if s == "true" then return true end + if s == "false" then return false end +--if s == "yes" then return true end --- Context style +--if s == "no" then return false end + return s +end + +local lpegmatch = lpeg.match +local P, S, R = lpeg.P, lpeg.S, lpeg.R +local C, Cc, Cf, Cg, Cs, Ct + = lpeg.C, lpeg.Cc, lpeg.Cf, lpeg.Cg, lpeg.Cs, lpeg.Ct + +--- terminals and low-level classes ----------------------------------- +--- note we could use the predefined ones from lpeg.patterns +local dot = P"." +local colon = P":" +local featuresep = S",;" +local slash = P"/" +local equals = P"=" +local lbrk, rbrk = P"[", P"]" + +local spacing = S" \t\v" +local ws = spacing^0 + +local digit = R"09" +local alpha = R("az", "AZ") +local anum = alpha + digit +local decimal = digit^1 * (dot * digit^0)^-1 + +--- modifiers --------------------------------------------------------- +--[[doc-- + The slash notation: called “modifiers” (Kew) or “font options” + (Robertson, Goosens) + we only support the shorthands for italic / bold / bold italic + shapes, the rest is ignored. +--doc]]-- +local style_modifier = (P"BI" + P"IB" + P"bi" + P"ib" + S"biBI") + / stringlower +local other_modifier = P"S=" * decimal --- optical size; unsupported + + P"AAT" + P"aat" --- apple stuff; unsupported + + P"ICU" + P"icu" --- not applicable + + P"GR" + P"gr" --- sil stuff; unsupported +local garbage_modifier = ((1 - colon - slash)^0 * Cc(false)) +local modifier = slash * (other_modifier --> ignore + + Cs(style_modifier) --> collect + + garbage_modifier) --> warn +local modifier_list = Cg(Ct(modifier^0), "modifiers") + +--- lookups ----------------------------------------------------------- +local fontname = C((1-S"/:(")^1) --- like luatex-fonts +local prefixed = P"name:" * ws * Cg(fontname, "name") + + P"file:" * ws * Cg(fontname, "file") +local unprefixed = Cg(fontname, "anon") +local path_lookup = lbrk * Cg(C((1-rbrk)^1), "path") * rbrk + +--- features ---------------------------------------------------------- +local field = (anum + S"+-.")^1 --- sic! +--- assignments are “lhs=rhs” +--- switches are “+key” | “-key” +local assignment = C(field) * ws * equals * ws * (field / toboolean) +local switch = P"+" * ws * C(field) * Cc(true) + + P"-" * ws * C(field) * Cc(false) + + C(field) * Cc(true) -- catch crap +local feature_expr = ws * Cg(assignment + switch) * ws +local feature_list = Cf(Ct"" + * feature_expr + * (featuresep * feature_expr)^0 + , rawset) + * featuresep^-1 + +--- other ------------------------------------------------------------- +--- This rule is present in the original parser. It sets the “sub” +--- field of the specification which allows addressing a specific +--- font inside a TTC container. Neither in Luatex-Fonts nor in +--- Luaotfload is this documented, so we might as well silently drop +--- it. However, as backward compatibility is one of our prime goals we +--- just insert it here and leave it undocumented until someone cares +--- to ask. (Note: afair subfonts are numbered, but this rule matches a +--- string; I won’t mess with it though until someone reports a +--- problem.) +--- local subvalue = P("(") * (C(P(1-S("()"))^1)/issub) * P(")") -- for Kim +--- Who’s Kim? +--- Note to self: subfonts apparently start at index 0. Tested with +--- Cambria.ttc that includes “Cambria Math” at 0 and “Cambria” at 1. +--- Other values cause luatex to segfault. +local subfont = P"(" * Cg((1 - S"()")^1, "sub") * P")" +--- top-level rules --------------------------------------------------- +--- \font\foo=<specification>:<features> +local features = Cg(feature_list, "features") +local specification = (prefixed + unprefixed) + * subfont^-1 + * modifier_list^-1 +local font_request = Ct(path_lookup * (colon^-1 * features)^-1 + + specification * (colon * features)^-1) + +-- lpeg.print(font_request) +--- new parser: 632 rules +--- old parser: 230 rules + +local import_values = { + --- That’s what the 1.x parser did, not quite as graciously, + --- with an array of branch expressions. + -- "style", "optsize",--> from slashed notation; handled otherwise + "lookup", "sub" --[[‽]], "mode", +} + +local lookup_types = { "anon", "file", "name", "path" } + +local select_lookup = function (request) + for i=1, #lookup_types do + local lookup = lookup_types[i] + local value = request[lookup] + if value then + return lookup, value + end + end +end + +local supported = { + b = "bold", + i = "italic", + bi = "bolditalic", + aat = false, + icu = false, + gr = false, +} + +local handle_slashed = function (modifiers) + local style, optsize + for i=1, #modifiers do + local mod = modifiers[i] + if supported[mod] then + style = supported[mod] + --elseif stringfind(v, "^s=") then + elseif stringsub(v, 1, 2) == "s=" then + local val = stringsub(v, 3) + optsize = val + elseif stylename == false then + report("log", 0, + "load", "unsupported font option: %s", v) + elseif not stringis_empty(v) then + style = stringgsub(v, "[^%a%d]", "") + end end - specification.features.normal = fonts.handlers.otf.features.normalize(feature_list) + return style, optsize +end + +--- spec -> spec +local handle_request = function (specification) + local request = lpegmatch(font_request, + specification.specification) + local lookup, name = select_lookup(request) + request.features = set_default_features(request.features) + + if name then + specification.name = name + specification.lookup = lookup or specification.lookup + end + + if request.modifiers then + local style, optsize = handle_slashed(request.modifiers) + specification.style, specification.optsize = style, optsize + end + + for n=1, #import_values do + local feat = import_values[n] + local newvalue = request.features[feat] + if newvalue then + specification[feat] = request.features[feat] + request.features[feat] = nil + end + end + + --- The next line sets the “rand” feature to “random”; I haven’t + --- investigated it any further (luatex-fonts-ext), so it will + --- just stay here. + specification.features.normal + = fonts.handlers.otf.features.normalize(request.features) return specification end -fonts.definers.registersplit(":",colonized,"cryptic") -fonts.definers.registersplit("", colonized,"more cryptic") -- catches \font\text=[names] - ---- TODO below section is literally the same in luatex-fonts-def ---- why is it here? ---function fonts.definers.applypostprocessors(tfmdata) --- local postprocessors = tfmdata.postprocessors --- if postprocessors then --- for i=1,#postprocessors do --- local extrahash = postprocessors[i](tfmdata) -- after scaling etc --- if type(extrahash) == "string" and extrahash ~= "" then --- -- e.g. a reencoding needs this --- extrahash = string.gsub(lower(extrahash),"[^a-z]","-") --- tfmdata.properties.fullname = format("%s-%s",tfmdata.properties.fullname,extrahash) --- end --- end --- end --- return tfmdata ---end +local compare_requests = function (spec) + local old = old_behavior(spec) + local new = handle_request(spec) + return new +end + +--fonts.definers.registersplit(":", compare_requests, "cryptic") +--fonts.definers.registersplit("", compare_requests, "more cryptic") -- catches \font\text=[names] + +fonts.definers.registersplit(":", handle_request, "cryptic") +fonts.definers.registersplit("", handle_request, "more cryptic") -- catches \font\text=[names] + +--fonts.definers.registersplit(":",old_behavior,"cryptic") +--fonts.definers.registersplit("", old_behavior,"more cryptic") -- catches \font\text=[names] + ---[[ end included font-ltx.lua ]] --[[doc-- diff --git a/luaotfload.dtx b/luaotfload.dtx index 18e01d8..6392c64 100644 --- a/luaotfload.dtx +++ b/luaotfload.dtx @@ -130,6 +130,8 @@ and the derived files \setsansfont[Ligatures=TeX,Scale=MatchLowercase]{Iwona Medium} %setmathfont{XITS Math} +\usepackage{hologo} + \newcommand\TEX {\TeX\xspace} \newcommand\LUA {Lua\xspace} \newcommand\PDFTEX {pdf\TeX\xspace} @@ -160,6 +162,8 @@ and the derived files \renewcommand\partname{Part}%% gets rid of the stupid “file” heading +\usepackage{syntax}%% bnf for font request syntax + \VerbatimFootnotes \begin{document} \DocInput{luaotfload.dtx}% @@ -244,56 +248,177 @@ and the derived files % % \section{Loading Fonts} % -% \identifier{luaotfload} supports an extended font loading syntax: +% \identifier{luaotfload} supports an extended font request syntax: % -% \begin{center} +% \begin{quote} % |\font\foo={|% % \meta{prefix}|:|% % \meta{font name}|:|% % \meta{font features}|}|% % \meta{\TEX font features} -% \end{center} +% \end{quote} % % \noindent % The curly brackets are optional and escape the spaces in the enclosed -% font name (alternatively, double quotes serve the same purpose). -% The individual parts of the syntax are: -% -% \paragraph{Prefix} -% -% The \meta{prefix} is either |file:| or |name:|. -% It determines whether the font loader should interpret the request as a -% file name or font name, respectively, which again influences how it -% will attempt to locate the font. -% The prefix can be omitted, in which case |name:| is assumed. -% -%% \iffalse%% how am i supposed to friggin comment stuff in a dtx??? -%% TODO -%% it would appear that the next paragraph is incorrect; I get -%% name: lookups regardless unless the font file is actually -%% in CWD -%% \fi -%% For compatibility with \XETEX, surrounding the \meta{font name} with -%% square brackets is synonymous to using the |file:| prefix. +% font name. +% Alternatively, double quotes serve the same purpose. +% A selection of individual parts of the syntax are discussed below; +% for a more formal description see figure \ref{font-syntax}. % +% \begin{figure}[b] +% \setlength\grammarparsep{12pt plus 2pt minus 2pt} +% \setlength\grammarindent{5cm} +% \begingroup +% \small +% \begin{grammar} +% <definition> ::= `\\font', {\sc csname}, `=', <font request>, [ <size> ] ; +% +% <size> ::= `at', {\sc dimension} ; +% +% <font request> ::= `"', <unquoted font request> `"' +% \alt `{', <unquoted font request> `}' +% \alt <unquoted font request> ; +% +% <unquoted font request> ::= <specification>, [`:', <feature list> ] +% \alt `[', <path lookup> `]', [ [`:'], <feature list> ] ; +% +% <specification> ::= <prefixed spec>, [ <subfont no> ], \{ <modifier> \} +% \alt <anon lookup>, \{ <modifier> \} ; +% +% <prefixed spec> ::= `file:', <file lookup> +% \alt `name:', <name lookup> ; +% +% <file lookup> ::= \{ <name character> \} ; +% +% <name lookup> ::= \{ <name character> \} ; +% +% <anon lookup> ::= {\sc tfmname} | <name lookup> ; +% +% <path lookup> ::= \{ {\sc all_characters} - `]' \} ; +% +% <modifier> ::= `/', (`i' | `b' | `bi' | `ib') ; +% +% <subfont no> ::= `(', \{ {\sc digit} \}, `)' ; +% +% <feature list> ::= <feature expr>, \{ `;', <feature expr> \} ; +% +% <feature expr> ::= {\sc feature_id}, `=', {\sc feature_value} +% \alt <feature switch>, {\sc feature_id} ; +% +% <feature switch> ::= `+' | `-' ; +% +% <name character> ::= {\sc all_characters} - ( `(' | `/' | `:' ) ; +% \end{grammar} +% \endgroup +% \caption{Font request syntax. +% Braces or double quotes around the +% \emphasis{specification} rule will +% preserve whitespace in file names. +% In addition to the font style modifiers +% (\emphasis{slash-notation}) given above, there +% are others that are recognized but will be silently +% ignored: {\ttfamily aat}, +% {\ttfamily icu}, and +% {\ttfamily gr}. +% The special terminals are: +% {\sc feature\textunderscore id} for a valid font +% feature name and +% {\sc feature\textunderscore value} for the corresponding +% value. +% {\sc tfmname} is the name of a \abbrev{tfm} file. +% {\sc digit} again refers to bytes 48--57, and +% {\sc all\textunderscore characters} to all byte values. +% {\sc csname} and {\sc dimension} are the \TEX concepts.} +% \label{font-syntax} +% \end{figure} +% +% \subsection{Prefix -- the \identifier{luaotfload}\space Way} +% +% In \identifier{luaotfload}, the canonical syntax for font requests +% requires a \emphasis{prefix}: +% \begin{quote} +% |\font\fontname=|\meta{prefix}|:|\meta{fontname}\dots +% \end{quote} +% where \meta{prefix} is either \verb|file:| or \verb|name:|. +% It determines whether the font loader should interpret the request as +% a \emphasis{file name} or +% \emphasis{font name}, respectively, +% which again influences how it will attempt to locate the font. +% Examples for font names are +% “Latin Modern Italic”, +% “GFS Bodoni Rg”, and +% “PT Serif Caption” +% -- they are the human readable identifiers +% usually listed in drop-down menus and the like. % In order for fonts installed both in system locations and in your % \fileent{texmf} to be accessible by font name, \identifier{luaotfload} must % first collect the metadata included in the files. -% Please refer to section ~\ref{sec:fontdb} below for instructions on how to +% Please refer to section~\ref{sec:fontdb} below for instructions on how to % create the database. % -% \paragraph{Font name} +% File names are whatever your file system allows them to be, except +% that that they may not contain the characters +% \verb|(|, +% \verb|:|, and +% \verb|/|. +% As obvious from the last exception, the \verb|file:| lookup will +% not process paths to the font location -- only those +% files found when generating the database are addressable this way. +% Continue below in the \XETEX section if you need to load your fonts +% by path. +% The file names corresponding to the example font names above are +% \fileent{lmroman12-italic.otf}, +% \fileent{GFSBodoni.otf}, and +% \fileent{PTZ56F.ttf}. +% +% \subsection{\hologo{XeTeX} Compatibility Layer} +% +% In addition to the regular prefixed requests, \identifier{luaotfload} +% accepts loading fonts the \XETEX way. +% There are again two modes: bracketed and unbracketed. +% A bracketed request looks as follows. % -% The \meta{font name} can be either a font filename or actual font -% name based on the \meta{prefix} as mentioned above. +% \begin{quote} +% |\font\fontname=[|\meta{path to file}|]| +% \end{quote} +% +% \noindent +% Inside the square brackets, every character except for a closing +% bracket is permitted, allowing for specifying paths to a font file. +% Naturally, path-less file names are equally valid and processed the +% same way as an ordinary \verb|file:| lookup. % -% A filename request may optionally include the absolute path to the font file, -% allowing for fonts outside the standard locations to be loaded as well. -% If no path is specified, then \identifier{kpathsea} is used to locate the -% font (which will typically be in the \fileent{texmf} tree or the -% current directory). +% \begin{quote} +% |\font\fontname=|\meta{font name} \dots +% \end{quote} % -% \subparagraph{Examples for loading by file name} +% Unbracketed (or, for lack of a better word: \emphasis{anonymous}) +% font requests resemble the conventional \TEX syntax. +% However, they have a broader spectrum of possible interpretations: +% before anything else, \identifier{luaotfload} attempts to load a +% traditional \TEX Font Metric (\abbrev{tfm} or \abbrev{ofm}). +% If this fails, it performs a \verb|name:| lookup, which itself will +% fall back to a \verb|file:| lookup if no database entry matches +% \meta{font name}. +% +% Furthermore, \identifier{luaotfload} supports the slashed (shorthand) +% font style notation from \XETEX. +% +% \begin{quote} +% |\font\fontname=|\meta{font name}|/|\meta{modifier}\dots +% \end{quote} +% +% \noindent +% Currently, four style modifiers are supported: +% \verb|i| for italic shape, +% \verb|b| for bold weight, +% \verb|bi| or \verb|ib| for the combination of both. +% Other “slashed” modifiers are too specific to the \XETEX engine and +% have no meaning in \LUATEX. +% +% \subsection{Examples} +% +% \subsubsection{Loading by File Name} % % For example, conventional \abbrev{type1} font can be loaded with a \verb|file:| % request like so: @@ -305,8 +430,11 @@ and the derived files % \end{quote} % % The \OpenType version of Janusz Nowacki’s font \emphasis{Antykwa -% Półtawskiego} (in \TEX Live) in its condensed variant can be loaded as -% follows: +% Półtawskiego}\footnote{% +% \url{http://jmn.pl/antykwa-poltawskiego/}, also available in +% in \TEX Live. +% } +% in its condensed variant can be loaded as follows: % % \begin{quote} % \begin{verbatim} @@ -324,7 +452,7 @@ and the derived files % \end{verbatim} % \end{quote} % -% \subparagraph{Examples for loading by font name} +% \subsubsection{Loading by Font Name} % % The \verb|name:| lookup does not depend on cryptic filenames: % @@ -360,9 +488,62 @@ and the derived files % \end{verbatim} % \end{quote} % -% \paragraph{Font features} +% \subsubsection{Modifiers} +% +% If the entire \emphasis{Iwona} family\footnote{% +% \url{http://jmn.pl/kurier-i-iwona/}, +% also in \TEX Live. +% } +% is installed in some location accessible by \identifier{luaotfload}, +% the regular shape can be loaded as follows: +% +% \begin{quote} +% \begin{verbatim} +% \font\iwona=Iwona at 20pt +% \end{verbatim} +% \end{quote} +% +% \noindent +% To load the most common of the other styles, the slash notation can +% be employed as shorthand: % -% \meta{font features} is semicolon-separated list of feature +% \begin{quote} +% \begin{verbatim} +% \font\iwonaitalic =Iwona/i at 20pt +% \font\iwonabold =Iwona/b at 20pt +% \font\iwonabolditalic=Iwona/bi at 20pt +% \end{verbatim} +% \end{quote} +% +% \noindent +% which is equivalent to these full names: +% +% \begin{quote} +% \begin{verbatim} +% \font\iwonaitalic ="Iwona Italic" at 20pt +% \font\iwonabold ="Iwona Bold" at 20pt +% \font\iwonabolditalic="Iwona BoldItalic" at 20pt +% \end{verbatim} +% \end{quote} +% +% \section{Font features} +% +% \emphasis{Font features} are the second to last component in the +% general scheme for font requests: +% +% \begin{quote} +% |\font\foo={|% +% \meta{prefix}|:|% +% \meta{font name}|:|% +% \meta{font features}|}|% +% \meta{\TEX font features} +% \end{quote} +% +% \noindent +% If style modifiers are present (\XETEX style), they must precede +% \meta{font features}. +% +% The element \meta{font features} is a semicolon-separated list of feature % tags\footnote{% % Cf. \url{http://www.microsoft.com/typography/otspec/featurelist.htm}. % } @@ -1003,7 +1184,7 @@ luaotfload.prefer_merge = config.luaotfload.prefer_merge or true luaotfload.module = { name = "luaotfload", version = 2.2, - date = "2013/04/15", + date = "2013/04/29", description = "OpenType layout system.", author = "Elie Roux & Hans Hagen", copyright = "Elie Roux", @@ -1014,11 +1195,12 @@ local luatexbase = luatexbase local type, next = type, next local setmetatable = setmetatable +local find_file = kpse.find_file +local lfsisfile = lfs.isfile local stringfind = string.find -local stringsub = string.sub -local stringmatch = string.match local stringformat = string.format -local find_file = kpse.find_file +local stringmatch = string.match +local stringsub = string.sub local add_to_callback, create_callback = luatexbase.add_to_callback, luatexbase.create_callback @@ -1328,7 +1510,7 @@ add_to_callback("find_vf_file", loadmodule"lib-dir.lua" --- required by luaofload-database.lua loadmodule"override.lua" --- “luat-ovr” -logs.set_loglevel(0) +logs.set_loglevel(config.luaotfload.loglevel or 2) % \end{macrocode} % \CONTEXT does not support ofm, these lines were added in order to make it @@ -1350,23 +1532,101 @@ loadmodule"database.lua" --- “font-nms” loadmodule"colors.lua" --- “font-clr” % \end{macrocode} -% This hack makes fonts called with file method found by fonts.names.resolve -% instead of just trying to find them with \identifier{kpse}. -% It is necessary in cases when font files are not reachable by -% \identifier{kpse} but present in the database, a quite common case -% under Linux. -% -% As of 2013-04-24 we have a workaround in the resolver that handles -% \verb|file:| lookups diverted this way. -% It requires some overhead due to additional extra data saved in the -% names database, and might vanish entirely once the font request syntax -% is understood. -% Until then it is considered a kludge, like the hack below. +% Relying on the \verb|name:| resolver for everything has been the source +% of permanent trouble with the database. +% With the introduction of the new syntax parser we now have enough +% granularity to distinguish between the \XETEX emulation layer and the +% genuine \verb|name:| and \verb|file:| lookups of \LUATEX-Fonts. +% Another benefit is that we can now easily plug in or replace new lookup +% behaviors if necessary. +% +% The name resolver remains untouched, but it calls +% \luafunction{fonts.names.resolve()} internally anyways (see +% \fileent{luaotfload-database.lua}). +% +% \begin{macrocode} + +local request_resolvers = fonts.definers.resolvers +local formats = fonts.formats +formats.ofm = "type1" + +% \end{macrocode} +% \identifier{luaotfload} promises easy access to system fonts. +% Without additional precautions, this cannot be achieved by +% \identifier{kpathsea} alone, because it searches only the +% \fileent{texmf} directories by default. +% Although it is possible for \identifier{kpathsea} to include extra +% paths by adding them to the \verb|OSFONTDIR| environment variable, +% this is still short of the goal »\emphasis{it just works!}«. +% When building the font database \identifier{luaotfload} scans +% system font directories anyways, so we already have all the +% information for looking sytem fonts. +% With the release version 2.2 the file names are indexed in the database +% as well and we are ready to resolve \verb|file:| lookups this way. +% Thus we no longer need to call the \identifier{kpathsea} library in +% most cases when looking up font files, only when generating the database. +% +% \begin{macrocode} +request_resolvers.file = function (specification) + --local found = fonts.names.crude_file_lookup(specification.name) + local found = fonts.names.crude_file_lookup_verbose(specification.name) + specification.name = found[1] + --if format then specification.forced = format end +end + +% \end{macrocode} +% We classify as \verb|anon:| those requests that have neither a +% prefix nor brackets. According to Khaled\footnote{% +% \url{https://github.com/phi-gamma/luaotfload/issues/4#issuecomment-17090553}. +% } +% they are the \XETEX equivalent of a \verb|name:| request, so we will be +% treating them as such. +% +% \begin{macrocode} + +--request_resolvers.anon = request_resolvers.name + +% \end{macrocode} +% There is one drawback, though. +% This syntax is also used for requesting fonts in \identifier{Type1} +% (\abbrev{tfm}, \abbrev{ofm}) format. +% These are essentially \verb|file:| lookups and must be caught before +% the \verb|name:| resolver kicks in, lest they cause the database to +% update. +% Even if we were to require the \verb|file:| prefix for all +% \identifier{Type1} requests, tests have shown that certain fonts still +% include further fonts (e.~g. \fileent{omlgcb.ofm} will ask for +% \fileent{omsecob.tfm}) \emphasis{using the old syntax}. +% For this reason, we introduce an extra check with an early return. % % \begin{macrocode} +local type1_formats = { "tfm", "ofm", } + +request_resolvers.anon = function (specification) + local name = specification.name + for i=1, #type1_formats do + local format = type1_formats[i] + if resolvers.findfile(name, format) then + specification.name = file.addsuffix(name, format) + return + end + end + request_resolvers.name(specification) +end -fonts.definers.resolvers.file = function (specification) - specification.name = fonts.names.resolve('', '', specification) +% \end{macrocode} +% Prior to version 2.2, \identifier{luaotfload} did not distinguish +% \verb|file:| and \verb|path:| lookups, causing complications with the +% resolver. +% Now we test if the requested name is an absolute path in the file +% system, otherwise we fall back to the \verb|file:| lookup. +% +% \begin{macrocode} +request_resolvers.path = function (specification) + local exists, _ = lfsisfile(specification.name) + if not exists then -- resort to file: lookup + request_resolvers.file(specification) + end end % \end{macrocode} @@ -1442,7 +1702,6 @@ local patch_defined_font = function (specification, size, id) if type(tfmdata) == "table" then call_callback("luaotfload.patch_font", tfmdata) end - -- inspect(table.keys(tfmdata)) return tfmdata end diff --git a/tests/pln-request-4-slashed.tex b/tests/pln-request-4-slashed.tex new file mode 100644 index 0000000..5e7d99e --- /dev/null +++ b/tests/pln-request-4-slashed.tex @@ -0,0 +1,12 @@ +\ifdefined\directlua\input luaotfload.sty\fi +\font\iwona =iwona at 20pt +\font\iwonabold =iwona/b at 20pt +\font\iwonaitalic =iwona/i at 20pt +\font\iwonabolditalic =iwona/bi at 20pt + +\def\test{foo bar baz \endgraf} +{\iwona \test} +{\iwonabold \test} +{\iwonaitalic \test} +{\iwonabolditalic \test} +\bye diff --git a/tests/pln-request-5-cached.tex b/tests/pln-request-5-cached.tex new file mode 100644 index 0000000..8ba4a5e --- /dev/null +++ b/tests/pln-request-5-cached.tex @@ -0,0 +1,18 @@ +\ifdefined\directlua + \directlua{config = config or { luaotfload = { } } + config.luaotfload.resolver = "cached" + config.luaotfload.loglevel = 5 } + \input luaotfload.sty +\fi + +\font\iwona =name:iwona at 20pt +\font\iwonabold =name:iwona/b at 20pt +\font\iwonaitalic =name:iwona/i at 20pt +\font\iwonabolditalic =name:iwona/bi at 20pt + +\def\test{foo bar baz \endgraf} +{\iwona \test} +{\iwonabold \test} +{\iwonaitalic \test} +{\iwonabolditalic \test} +\bye diff --git a/tests/pln-subfont-1.tex b/tests/pln-subfont-1.tex new file mode 100644 index 0000000..fb8e1e7 --- /dev/null +++ b/tests/pln-subfont-1.tex @@ -0,0 +1,12 @@ +\ifdefined\directlua\input luaotfload.sty\fi +%% This requires the Cambria fonts from MS. +\directlua{ + inspect(fontloader.info"cambria.ttc") +} +%% Here we load both subfonts in the collection +%% with the not-quite documented subfont syntax. +\font\subfontone="file:cambria.ttc(0)" at 42pt +\font\subfonttwo="file:cambria.ttc(1)" at 42pt +\subfontone foo bar baz \endgraf +\subfonttwo foo bar baz \endgraf +\bye diff --git a/tests/pln-tfm.tex b/tests/pln-tfm.tex new file mode 100644 index 0000000..26fa738 --- /dev/null +++ b/tests/pln-tfm.tex @@ -0,0 +1,8 @@ +\ifdefined\directlua\input luaotfload.sty \fi +%% TFM’s can be loaded with a file: request ... +\font\antykwatorunska="file:rm-anttr" +%% or with an anonymous request, like in þe olde TeX: +\font\antykwatorunskabcap=ec-anttbcap +\antykwatorunska foo bar +\antykwatorunskabcap baz xyzzy +\bye |