local M = { } local alloc = require 'minim-alloc' local cb = require 'minim-callbacks' -- the standard table design local function init_resources() return { ExtGState = { }, ColorSpace = { }, Pattern = { }, Shading = { }, } end -- the module will use optex’s functions if present M.self_destruct = pdf.add_page_resource or false -- resource_data is a table that can contain any data. only the following keys -- are used by this module: -- -- _entry_ internal value; do not use -- value value in the resource dictionary -- write() called for writing the resource to the pdf; returns value -- -- of the last two, only one needs be present; if the value is missing it will -- be set at first use to the result of write(). -- local resource_data = init_resources() -- name ↦ { [write = ... | value = ...], ... } -- function M.add_resource(kind, name, resource) resource_data[kind][name] = resource end function M.get_resource(kind, name) return resource_data[kind][name] end function M.use_resource(kind, name) local res = resource_data[kind][name] if not res then return alloc.err('Unknown %s resource: %s', kind, name) end res.value = res.value or res:write() if not res._entry_ then -- first-use res._entry_ = string.format('%s %s', alloc.pdf_name(name), res.value) if M.self_destruct then M.self_destruct(kind, name, res.value) end end return res._entry_ end -- global resources are mainly for pgf compatibility: it contains entries to -- the resource dictionaries that will be added for every page. -- local global_resources = init_resources() -- name ↦ '/Key ' -- function M.add_global_resource(kind, name, value) local r = global_resources[kind] if M.self_destruct then M.self_destruct(kind, name, value) elseif name == nil then -- used for pgf compatibility if value ~= '' then r[''] = r[''] and string.format('%s %s', global_resources[kind][''], value) or value end else r[name] = string.format('%s %s', alloc.pdf_name(name), value) end end -- we can now provide optex’s interface if not M.self_destruct then pdf.add_page_resource = M.add_global_resource end -- for nonglobal resources, every use must be marked with a late_lua node. from -- those, the M.use_resouce() function will be called automatically. -- local page_resources = init_resources() -- name ↦ '/Key ' -- function _with_pdf_resource_(kind, name) -- global, for use in latelua page_resources[kind][name] = M.use_resource(kind, name) end function M.use_resource_node(kind, name) local n = node.new('whatsit', 'late_lua') n.data = string.format('_with_pdf_resource_("%s", "%s")', kind, name) return n end alloc.luadef('withpdfresource', function() node.write(M.use_resource_node(token.scan_string(), token.scan_string())) end, 'protected') -- construction and caching of resource dictionaries. -- local previous_dicts = init_resources() -- pdf dict ↦ objnum -- local function make_resource_entry(kind) local arr = { } for _, entry in pairs(page_resources[kind]) do table.insert(arr, entry) end for _, entry in pairs(global_resources[kind]) do table.insert(arr, entry) end if #arr == 0 then return false end table.sort(arr) arr[0] = '<<'; table.insert(arr, '>>') local dict = table.concat(arr, ' ', 0) local objnum = previous_dicts[kind][dict] if not objnum then objnum = pdf.obj(dict) pdf.refobj(objnum) previous_dicts[kind][dict] = objnum end return string.format('/%s %d 0 R', kind, objnum) end local removal_regex = { ExtGState = ' */ExtGState *%d+ +0 *R', ColorSpace = ' */ColorSpace *%d+ +0 *R', Pattern = ' */Pattern *%d+ +0 *R', Shading = ' */Shading *%d+ +0 *R' } local function update_page_resources(shippingout) if M.self_destruct then return end local resources = shippingout and pdf.getpageresources() or pdf.getxformresources() or '' for kind, _ in pairs(page_resources) do resources = string.gsub(resources, removal_regex[kind], '') local entry = make_resource_entry(kind) if entry then resources = resources == '' and entry or string.format('%s %s', resources, entry) end end -- update page resources if shippingout then pdf.setpageresources(resources) else pdf.setxformresources(resources) end page_resources = init_resources() end cb.register('finish_pdfpage', update_page_resources) -- for pgf compatibility, reroute the pgf resource registration functions to -- our global resource pool. -- alloc.luadef('minim:pgf:fix:luaside', function() alloc.luadef('pgf@sys@addpdfresource@extgs@plain', function() M.add_global_resource('ExtGState', nil, token.scan_string()) end) alloc.luadef('pgf@sys@addpdfresource@patterns@plain', function() M.add_global_resource('Pattern', nil, token.scan_string()) end) alloc.luadef('pgf@sys@addpdfresource@colorspaces@plain', function() M.add_global_resource('ColorSpace', nil, token.scan_string()) end) end) return M