----------------------------------------------------------------------- -- FILE: luaotfload-loaders.lua -- DESCRIPTION: Luaotfload callback handling -- REQUIREMENTS: luatex v.0.80 or later; package lualibs -- AUTHOR: Philipp Gesang -- AUTHOR: Hans Hagen, Khaled Hosny, Elie Roux, David Carlisle ----------------------------------------------------------------------- assert(luaotfload_module, "This is a part of luaotfload and should not be loaded independently") { name = "luaotfload-loaders", version = "3.28", --TAGVERSION date = "2024-02-14", --TAGDATE description = "luaotfload submodule / callback handling", license = "GPL v2.0" } ----------------------------------------------------------------------- if not lualibs then error "this module requires Luaotfload" end if not luaotfload then error "this module requires Luaotfload" end local logreport = luaotfload.log and luaotfload.log.report or print local function lua_reader (specification) local fullname = specification.resolved if fullname then local loader = loadfile (fullname) loader = loader and loader () return loader and loader (specification) end end local function eval_reader (specification) local eval = specification.eval if not eval or type (eval) ~= "function" then return nil end logreport ("both", 0, "loaders", "eval: found tfmdata for %q, injecting.", specification.name) return eval () end local function unsupported_reader (format) return function (specification) logreport ("both", 4, "loaders", "font format %q unsupported; cannot load %s.", format, tostring (specification.name)) end end local type1_reader = fonts.readers.afm local tfm_reader = fonts.readers.tfm local function install_formats () local fonts = fonts if not fonts then return false end local readers = fonts.readers local sequence = readers.sequence local seqset = table.tohash (sequence) local formats = fonts.formats if not readers or not formats then return false end local function aux (which, reader) if not which or type (which) ~= "string" or not reader or type (reader) ~= "function" then logreport ("both", 2, "loaders", "Error installing reader for %q.", which) return false end formats [which] = "type1" readers [which] = reader if not seqset [which] then logreport ("both", 3, "loaders", "Extending reader sequence for %q.", which) sequence [#sequence + 1] = which seqset [which] = true end return true end return aux ("evl", eval_reader) and aux ("lua", lua_reader) and aux ("pfa", unsupported_reader "pfa") and aux ("afm", type1_reader) and aux ("pfb", type1_reader) and aux ("tfm", tfm_reader) and aux ("ofm", tfm_reader) and aux ("dfont", unsupported_reader "dfont") end local function not_found_msg (specification, size, id) logreport ("both", 0, "loaders", "") logreport ("both", 0, "loaders", "--------------------------------------------------------") logreport ("both", 0, "loaders", "") logreport ("both", 0, "loaders", "Font definition failed for:") logreport ("both", 0, "loaders", "") logreport ("both", 0, "loaders", " > id : %d", id) logreport ("both", 0, "loaders", " > specification : %q", specification) if size > 0 then logreport ("both", 0, "loaders", " > size : %.2f pt", size / 2^16) end logreport ("both", 0, "loaders", "") logreport ("both", 0, "loaders", "--------------------------------------------------------") end local definers --- (string, spec -> size -> id -> tmfdata) hash_t do local hashed_cache = {} local fontdata_cache = fonts.hashes.identifiers local function register(fontdata, id) if fontdata and id then local hash = fontdata.properties.hash if not hash then logreport('both', 2, 'loaders', "Registering font %s with id %s with missing hash.", id, fontdata.properties.filename or "unknown") elseif not hashed_cache[hash] then hashed_cache[hash] = id logreport('both', 4, 'loaders', "Registering font %s with id %s and hash %s.", id, fontdata.properties.filename or "unknown", hash) end fontdata_cache[id] = fontdata end end function registered(hash) local id = hashed_cache[hash] return id, fontdata_cache[id] end fonts.definers.register = register fonts.definers.registered = registered local extra_hash_funcs = {} local function ltx_read(spec, size, id) spec = type(spec) == 'string' and fonts.definers.analyze(spec, size) or spec spec = fonts.definers.resolve(spec) local hash = fonts.constructors.hashinstance(spec) -- extra_hash_func allows the loader to determine additional hash parts -- depending on the actual font. This is used e.g. for variable fonts which -- should be hashed based on their designsize / modifiers too. local extra_hash_func = extra_hash_funcs[hash] if extra_hash_func then hash = hash .. ' @ ' .. table.join(extra_hash_func(spec), ' @ ') end local cached = registered(hash) if cached then return cached end -- already loaded local fontdata = fonts.definers.loadfont(spec) if not fontdata then logreport('both', 2, 'loaders', 'Failed to load font ' .. spec.name) return end -- If extra_hash_func was already set then the hash is already extended. -- Otherwise we have to do it now if requested. if not extra_hash_func then extra_hash_func = fontdata.properties.extra_hash_func if extra_hash_func then hash = hash .. ' @ ' .. table.join(extra_hash_func(spec), ' @ ') end end fontdata.properties.hash = hash register(fontdata, id) return fontdata end local function read(specification, size, id) local fontdata = ltx_read(specification, size, id) if not fontdata or id or tonumber(fontdata) then return fontdata end id = font.define(fontdata) register(fontdata, id) return id end local function patch (specification, size, id) local fontdata = ltx_read (specification, size, id) ----if not fontdata then not_found_msg (specification, size, id) end if type (fontdata) == "table" and fontdata.encodingbytes == 2 then --- We need to test for `encodingbytes` to avoid passing --- tfm fonts to `patch_font`. These fonts are fragile --- because they use traditional TeX font handling. luatexbase.call_callback ("luaotfload.patch_font", fontdata, specification, id) else luatexbase.call_callback ("luaotfload.patch_font_unsafe", fontdata, specification, id) end if not fontdata or id or tonumber(fontdata) then return fontdata end id = font.define(fontdata) register(fontdata, id) return id end local function mk_info (name) local definer = name == "patch" and patch or read return function (specification, size, id) logreport ("both", 0, "loaders", "defining font no. %d", id) logreport ("both", 0, "loaders", " > active font definer: %q", name) logreport ("both", 0, "loaders", " > spec %q", specification) logreport ("both", 0, "loaders", " > at size %.2f pt", size / 2^16) local result = definer (specification, size, id) if not result then return not_found_msg (specification, size, id) end if type (result) == "number" then logreport ("both", 0, "loaders", " > font definition yielded id %d", result) return result end logreport ("both", 0, "loaders", " > font definition successful") logreport ("both", 0, "loaders", " > name %q", result.name or "") logreport ("both", 0, "loaders", " > fontname %q", result.fontname or "") logreport ("both", 0, "loaders", " > fullname %q", result.fullname or "") logreport ("both", 0, "loaders", " > type %s", result.type or "") local spec = result.specification if spec then logreport ("both", 0, "loaders", " > file %q", spec.filename or "") logreport ("both", 0, "loaders", " > subfont %s", spec.sub or "") end return result end end definers = { patch = patch, generic = read, info_patch = mk_info "patch", info_generic = mk_info "generic", } end --[[doc-- We create callbacks for patching fonts on the fly, to be used by other packages. In addition to the regular \identifier{patch_font} callback there is an unsafe variant \identifier{patch_font_unsafe} that will be invoked even if the target font lacks certain essential tfmdata tables. The callbacks initially contain the empty function that we are going to override below. --doc]]-- local function purge_define_font () local cdesc = luatexbase.callback_descriptions "define_font" --- define_font is an “exclusive” callback, meaning that there can --- only ever be one entry. Everything beyond that would indicate --- that something is broken. local _, d = next (cdesc) if d then local i, d2 = next (cdesc, 1) if d2 then --> issue warning logreport ("both", 0, "loaders", "Callback table for define_font contains multiple entries: \z { [%d] = %q } -- seems fishy.", i, d2) end logreport ("log", 0, "loaders", "Entry %q present in define_font callback; overriding.", d) luatexbase.remove_from_callback ("define_font", d) end end local install_callbacks = function () local create_callback = luatexbase.create_callback local dummy_function = function () end create_callback ("luaotfload.patch_font", "simple", dummy_function) create_callback ("luaotfload.patch_font_unsafe", "simple", dummy_function) purge_define_font () local definer = config.luaotfload.run.definer or "patch" local selected_definer = definers[definer] luaotfload.define_font = selected_definer luatexbase.add_to_callback ("define_font", selected_definer, "luaotfload.define_font", 1) return true end return function () if not install_formats () then logreport ("log", 0, "loaders", "Error initializing OFM/PF{A,B} loaders.") return false end if not install_callbacks () then logreport ("log", 0, "loaders", "Error installing font loader callbacks.") return false end return true end -- vim:tw=79:sw=2:ts=2:expandtab