local M = {} local function log(...) end local function alog(...) end local function do_log(msg, ...) texio.write_nl('log', string.format(msg, ...)) end -- 1 capturing the callback mechanism local primitives = { } M.primitives = primitives primitives.register = callback.register primitives.find = callback.find primitives.list = callback.list -- use the ltluatex functions if needed local primitive_register = primitives.register if luatexbase then primitive_register = function(cb, f) if f == nil then luatexbase.remove_from_callback(cb, 'minim callback') elseif f == false then luatexbase.disable_callback(cb) else luatexbase.add_to_callback(cb, f, 'minim callback') end end end local own_callbacks = {} local callback_lists = {} local callback_stacks = {} -- 1 finding callbacks function M.find(cb) local f = own_callbacks[cb] if f == nil then return primitives.find(cb) else return f end end function M.list() local t = {} for n,f in pairs(callback_lists) do if f then t[n] = #f else t[n] = false end end -- no stack callbacks, since the active callback is in one of the two below for n,f in pairs(primitives.list()) do if f then t[n] = t[n] or true else t[n] = t[n] or false end end -- this might obscure primitive callbacks (this is intended) for n,f in pairs(own_callbacks) do if f then t[n] = t[n] or true else t[n] = t[n] or false end end return t end -- 1 registering callbacks local function register_simple(cb,f) -- prefer user-defined callbacks over built-in local own = own_callbacks[cb] log ('callback %s: %s (%s)', f == nil and 'removed' or f and 'set' or 'disabled', cb, own == nil and 'primitive' or 'user-defined') if own == nil then -- may be set to ‘false’ return primitive_register(cb, f) else own_callbacks[cb] = f or false -- ‘nil’ would delete the callback return -1 end end -- will be redefined later function M.register(cb, f) local list = callback_lists[cb] if list then if f == nil then return tex.error('Use ‘deregister’ for removing list callbacks') else list[#list+1] = f log('callback set: %s (#%d on list)', cb, #list) return -2 end end local stack = callback_stacks[cb] if stack then if f == nil then -- pop local p = stack[#stack] stack[#stack] = nil return register_simple(cb,p) else -- push stack[#stack+1] = M.find(cb) return register_simple(cb,f) end end return register_simple(cb, f) end function M.deregister(cb, f) local list = callback_lists[cb] if list then for i,g in ipairs(list) do if f == g then log('callback removed: %s (#%d on list)', cb, i) table.remove(list, i) return true, -2 end end return false end local stack = callback_stacks[cb] if stack then for i,g in ipairs(stack) do if f == g then log('callback removed: %s (#%d on stack)', cb, i) table.remove(stack, i) return true, -3 end end -- no return: fall through to next end if f == M.find(cb) then return true, register_simple(cb, nil) end end -- 1 lists of callback functions local function call_list_node (lst) return function (head, ...) local list = callback_lists[lst] for _,f in ipairs(list) do local newhead = f(head,...) if node.is_node(newhead) then head = newhead elseif newhead == false then return false end end return head end end local function call_list_data (lst) return function (str) local list = callback_lists[lst] for _,f in ipairs(list) do str = f(str) or str end return str end end local function call_list_simple (lst) return function (...) local list = callback_lists[lst] for _,f in ipairs(list) do f(...) end end end -- 1 creating and calling callbacks local function register_list (lst, fn) M.register (lst, fn(lst)) callback_lists[lst] = {} end local function stack_callback (cb) callback_stacks[cb] = {} end function M.new_callback (name, prop) own_callbacks[name] = false -- false means empty here if prop == 'stack' then stack_callback (name) elseif prop == 'node' then register_list (name, call_list_node) elseif prop == 'simple' then register_list (name, call_list_simple) elseif prop == 'data' then register_list (name, call_list_data) end end function M.call_callback (name, ...) local f = own_callbacks[name] if f then return f (...) else return false end end -- 1 replace the primitive registering -- TODO: preserve return values local primitively_registered = { } function M.primitiveregister(cb, f) local rv, _ if f == nil then f = primitively_registered[cb] if f == nil then rv = M.register(cb) else _, rv = M.deregister(cb, f) end else rv = M.register(cb, f) end alog(' through primitive interface') primitively_registered[cb] = f return rv end -- 1 initialisation -- save all registered callbacks if not luatexbase then for n,s in pairs(primitives.list()) do if s then do_log('save callback: %s', n) primitively_registered[n] = primitives.find(n) end end end -- string processing callbacks register_list ('process_input_buffer', call_list_data) register_list ('process_output_buffer', call_list_data) register_list ('process_jobname', call_list_data) -- node list processing callbacks register_list ('pre_linebreak_filter', call_list_node) register_list ('post_linebreak_filter', call_list_node) --register_list ('append_to_vlist_filter', call_list_node) -- TODO this breaks something register_list ('hpack_filter', call_list_node) register_list ('vpack_filter', call_list_node) register_list ('pre_output_filter', call_list_node) -- mlist_to_mlist and mlist_to_mlist M.new_callback ('mlist_to_mlist', 'node') M.new_callback ('mlist_to_hlist', 'stack') M.register ('mlist_to_hlist', node.mlist_to_hlist ) primitive_register ('mlist_to_hlist', function (head, ...) local newhead = M.call_callback ('mlist_to_mlist', head, ...) if newhead ~= true then head = newhead or head end newhead = M.call_callback ('mlist_to_hlist', head, ...) return newhead end) -- simple listable callbacks register_list ('contribute_filter', call_list_simple) register_list ('pre_dump', call_list_simple) register_list ('wrapup_run', call_list_simple) register_list ('finish_pdffile', call_list_simple) register_list ('finish_pdfpage', call_list_simple) register_list ('insert_local_par', call_list_simple) register_list ('ligaturing', call_list_simple) register_list ('kerning', call_list_simple) -- stack callbacks stack_callback ('hpack_quality') stack_callback ('vpack_quality') stack_callback ('hyphenate') stack_callback ('linebreak_filter') stack_callback ('buildpage_filter') stack_callback ('build_page_insert') -- process_rule M.new_callback ('process_rule', 'simple') primitive_register ('process_rule', function (rule, ...) local p = own_callbacks[rule.index] if p then p (rule, ...) else M.call_callback ('process_rule') end end) -- restore all registered callbacks for n,f in pairs(primitively_registered) do do_log('restore callback: %s', n) M.primitiveregister (n,f) end -- -- replace primitive callbacks callback.find = M.find callback.list = M.list callback.register = M.primitiveregister log = do_log alog = function(msg, ...) texio.write('log', string.format(msg, ...)) end return M