summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/minim/minim-pdfresources.lua
blob: 66d168f4ecc43b1219a19731e8853634e0df3de6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158

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 <value>'
--
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 <value>'
--
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