if not modules then modules = { } end modules ["extralibs"] = { version = "2.4", comment = "companion to luaotfload.lua", author = "Hans Hagen, Philipp Gesang", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "GPL v.2.0", } -- extralibs: set up an emulation layer to load additional Context -- libraries --===================================================================-- --- PREPARE --===================================================================-- local getmetatable = getmetatable local require = require local select = select local setmetatable = setmetatable local tonumber = tonumber local texattribute = tex.attribute local new_node = node.new local copy_node = node.copy local otffeatures = fonts.constructors.newfeatures "otf" ----------------------------------------------------------------------- --- namespace ----------------------------------------------------------------------- --- The “typesetters” namespace isn’t bad at all; there is no need --- to remove it after loading. typesetters = typesetters or { } local typesetters = typesetters typesetters.kerns = typesetters.kerns or { } local kerns = typesetters.kerns kerns.mapping = kerns.mapping or { } kerns.factors = kerns.factors or { } local kern_callback = "typesetters.kerncharacters" typesetters.kernfont = typesetters.kernfont or { } local kernfont = typesetters.kernfont ----------------------------------------------------------------------- --- node-ini ----------------------------------------------------------------------- nodes = nodes or { } --- should be present with luaotfload local bothways = function (t) return table.swapped (t, t) end local kerncodes = bothways({ [0] = "fontkern", [1] = "userkern", [2] = "accentkern", }) kerncodes.kerning = kerncodes.fontkern --- idiosyncrasy nodes.kerncodes = kerncodes nodes.skipcodes = bothways({ [ 0] = "userskip", [ 1] = "lineskip", [ 2] = "baselineskip", [ 3] = "parskip", [ 4] = "abovedisplayskip", [ 5] = "belowdisplayskip", [ 6] = "abovedisplayshortskip", [ 7] = "belowdisplayshortskip", [ 8] = "leftskip", [ 9] = "rightskip", [ 10] = "topskip", [ 11] = "splittopskip", [ 12] = "tabskip", [ 13] = "spaceskip", [ 14] = "xspaceskip", [ 15] = "parfillskip", [ 16] = "thinmuskip", [ 17] = "medmuskip", [ 18] = "thickmuskip", [100] = "leaders", [101] = "cleaders", [102] = "xleaders", [103] = "gleaders", }) ----------------------------------------------------------------------- --- node-res ----------------------------------------------------------------------- nodes.pool = nodes.pool or { } local pool = nodes.pool local kern = new_node("kern", nodes.kerncodes.userkern) local glue_spec = new_node "glue_spec" pool.kern = function (k) local n = copy_node(kern) n.kern = k return n end pool.gluespec = function (width, stretch, shrink, stretch_order, shrink_order) local s = copy_node(glue_spec) if width then s.width = width end if stretch then s.stretch = stretch end if shrink then s.shrink = shrink end if stretch_order then s.stretch_order = stretch_order end if shrink_order then s.shrink_order = shrink_order end return s end pool.glue = function (width, stretch, shrink, stretch_order, shrink_order) local n = new_node"glue" if not width then -- no spec elseif width == false or tonumber(width) then local s = copy_node(glue_spec) if width then s.width = width end if stretch then s.stretch = stretch end if shrink then s.shrink = shrink end if stretch_order then s.stretch_order = stretch_order end if shrink_order then s.shrink_order = shrink_order end n.spec = s else -- shared n.spec = copy_node(width) end return n end ----------------------------------------------------------------------- --- font-hsh ----------------------------------------------------------------------- --- some initialization resembling font-hsh local fonthashes = fonts.hashes local identifiers = fonthashes.identifiers --- was: fontdata local chardata = fonthashes.characters local quaddata = fonthashes.quads local markdata = fonthashes.marks local parameters = fonthashes.parameters --- ('a, 'a) hash -> (('a, 'a) hash -> 'a -> 'a) -> ('a, 'a) hash local setmetatableindex = function (t, f) local mt = getmetatable(t) if mt then mt.__index = f else setmetatable(t, { __index = f }) end return t end if not parameters then parameters = { } setmetatableindex(parameters, function(t, k) if k == true then return parameters[currentfont()] else local parameters = identifiers[k].parameters t[k] = parameters return parameters end end) fonthashes.parameters = parameters end if not chardata then chardata = { } setmetatableindex(chardata, function(t, k) if k == true then return chardata[currentfont()] else local tfmdata = identifiers[k] if not tfmdata then --- unsafe tfmdata = font.fonts[k] end if tfmdata then local characters = tfmdata.characters t[k] = characters return characters end end end) fonthashes.characters = chardata end if not quaddata then quaddata = { } setmetatableindex(quaddata, function(t, k) if k == true then return quads[currentfont()] else local parameters = parameters[k] local quad = parameters and parameters.quad or 0 t[k] = quad return quad end end) fonthashes.quads = quaddata end if not markdata then markdata = { } setmetatableindex(markdata, function(t, k) if k == true then return marks[currentfont()] else local resources = { } if identifiers[k] then resources = identifiers[k].resources or { } end local marks = resources.marks or { } t[k] = marks return marks end end) fonthashes.marks = markdata end --- next stems from the multilingual interface interfaces = interfaces or { } interfaces.variables = interfaces.variables or { } interfaces.variables.max = "max" ----------------------------------------------------------------------- --- attr-ini ----------------------------------------------------------------------- attributes = attributes or { } --- to be removed with cleanup local hidden = { a_kerns = luatexbase.new_attribute("typo-krn:a_kerns", true), a_fontkern = luatexbase.new_attribute("typo-krn:a_fontkern", true), } attributes.private = attributes.private or function (attr_name) local res = hidden[attr_name] if not res then res = luatexbase.new_attribute(attr_name) end return res end if luatexbase.get_unset_value then attributes.unsetvalue = luatexbase.get_unset_value() else -- old luatexbase attributes.unsetvalue = (luatexbase.luatexversion < 37) and -1 or -2147483647 end ----------------------------------------------------------------------- --- luat-sto ----------------------------------------------------------------------- --- Storage is so ridiculously well designed in Context it’s a pity --- we can’t just force every package author to use it. storage = storage or { } storage.register = storage.register or function (...) local t = { ... } --- sorry return t end ----------------------------------------------------------------------- --- node-fin ----------------------------------------------------------------------- local plugin_store = { } local installattributehandler = function (plugin) --- Context has some load() magic here. plugin_store[plugin.name] = plugin.processor end nodes.installattributehandler = installattributehandler ----------------------------------------------------------------------- --- node-tsk ----------------------------------------------------------------------- nodes.tasks = nodes.tasks or { } nodes.tasks.enableaction = function () end ----------------------------------------------------------------------- --- core-ctx ----------------------------------------------------------------------- commands = commands or { } --===================================================================-- --- LOAD --===================================================================-- --- we should be ready at this moment to insert the libraries require "luaotfload-typo-krn" --- typesetters.kerns require "luaotfload-letterspace" --- typesetters.kernfont --===================================================================-- --- CLEAN --===================================================================-- --- interface ----------------------------------------------------------------------- local factors = kerns.factors local mapping = kerns.mapping local unsetvalue = attributes.unset_value local process_kerns = plugin_store.kern --- kern_callback : normal --- · callback: process_kerns --- · enabler: enablecharacterkerning --- · disabler: disablecharacterkerning --- · interface: kerns.set --- kernfont_callback : fontwise --- · callback: kernfont.handler --- · enabler: enablefontkerning --- · disabler: disablefontkerning --- callback wrappers --- (node_t -> node_t) -> string -> string list -> bool local registered_as = { } --- procname -> callbacks local add_processor = function (processor, name, ...) local callbacks = { ... } for i=1, #callbacks do luatexbase.add_to_callback(callbacks[i], processor, name) end registered_as[name] = callbacks --- for removal return true end --- string -> bool local remove_processor = function (name) local callbacks = registered_as[name] if callbacks then for i=1, #callbacks do luatexbase.remove_from_callback(callbacks[i], name) end return true end return false --> unregistered end --- we use the same callbacks as a node processor in Context --- unit -> bool local enablecharacterkerning = function ( ) return add_processor(function (head) return process_kerns("kerns", hidden.a_kerns, head) end, "typesetters.kerncharacters", "pre_linebreak_filter", "hpack_filter" ) end --- unit -> bool local disablecharacterkerning = function ( ) return remove_processor "typesetters.kerncharacters" end kerns.enablecharacterkerning = enablecharacterkerning kerns.disablecharacterkerning = disablecharacterkerning --- now for the simplistic variant --- unit -> bool local enablefontkerning = function ( ) return add_processor( kernfont.handler , "typesetters.kernfont" , "pre_linebreak_filter" , "hpack_filter") end --- unit -> bool local disablefontkerning = function ( ) return remove_processor "typesetters.kernfont" end --- fontwise kerning uses a font property for passing along the --- letterspacing factor local fontkerning_enabled = false --- callback state --- fontobj -> float -> unit local initializefontkerning = function (tfmdata, factor) if factor ~= "max" then factor = tonumber(factor) or 0 end if factor == "max" or factor ~= 0 then local fontproperties = tfmdata.properties if fontproperties then --- hopefully this field stays unused otherwise fontproperties.kerncharacters = factor end if not fontkerning_enabled then fontkerning_enabled = enablefontkerning() end end end --- like the font colorization, fontwise kerning is hooked into the --- feature mechanism otffeatures.register { name = "kernfactor", description = "kernfactor", initializers = { base = initializefontkerning, node = initializefontkerning, } } --[[doc-- The “letterspace” feature is essentially identical with the above “kernfactor” method, but scales the factor to percentages to match Xetex’s behavior. (See the Xetex reference, page 5, section 1.2.2.) Since Xetex doesn’t appear to have a (documented) “max” keyword, we assume all input values are numeric. --doc]]-- local initializecompatfontkerning = function (tfmdata, percentage) local factor = tonumber (percentage) if not factor then logs.names_report ("both", 0, "letterspace", "Invalid argument to letterspace: %s (type %q), " .. "was expecting percentage as Lua number instead.", percentage, type (percentage)) return end return initializefontkerning (tfmdata, factor * 0.01) end otffeatures.register { name = "letterspace", description = "letterspace", initializers = { base = initializecompatfontkerning, node = initializecompatfontkerning, } } kerns.set = nil local characterkerning_enabled = false kerns.set = function (factor) if factor ~= "max" then factor = tonumber(factor) or 0 end if factor == "max" or factor ~= 0 then if not characterkerning_enabled then enablecharacterkerning() characterkerning_enabled = true end local a = factors[factor] if not a then a = #mapping + 1 factors[factors], mapping[a] = a, factor end factor = a else factor = unsetvalue end texattribute[hidden.a_kerns] = factor return factor end ----------------------------------------------------------------------- --- options ----------------------------------------------------------------------- kerns .keepligature = false --- supposed to be of type function kerns .keeptogether = false --- supposed to be of type function kernfont.keepligature = false --- supposed to be of type function kernfont.keeptogether = false --- supposed to be of type function ----------------------------------------------------------------------- --- erase fake Context layer ----------------------------------------------------------------------- attributes = nil --commands = nil --- used in lualibs storage = nil --- not to confuse with utilities.storage nodes.tasks = nil collectgarbage"collect" --[[example-- \input luaotfload.sty \def\setcharacterkerning#1{% #1 factor : float \directlua{typesetters.kerns.set(0.618)}% } %directlua{typesetters.kerns.enablecharacterkerning()} \font\iwona = "name:Iwona:mode=node" at 42pt \font\lmregular = "name:Latin Modern Roman:mode=node" at 42pt {\iwona foo {\setcharacterkerning{0.618}% bar} baz} {\lmregular foo {\setcharacterkerning{0.125}ff fi ffi fl Th} baz} {\lmregular \directlua{ %% I’m not exactly sure how those work typesetters.kerns.keepligature = function (start) print("[liga]", start) return true end typesetters.kerns.keeptogether = function (start) print("[keeptogether]", start) return true end}% foo {\setcharacterkerning{0.125}ff fi ffi fl Th} baz} \bye --example]]-- -- vim:ts=2:sw=2:expandtab