lpdf-ren.lmt /size: 12 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['lpdf-ren'] = {
2    version   = 1.001,
3    comment   = "companion to lpdf-ini.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- rendering
10
11local tostring, tonumber, next = tostring, tonumber, next
12local concat = table.concat
13local formatters = string.formatters
14local settings_to_array = utilities.parsers.settings_to_array
15local getrandom = utilities.randomizer.get
16
17local pdfbackend          = backends.registered.pdf
18local nodeinjections      = pdfbackend.nodeinjections
19local codeinjections      = pdfbackend.codeinjections
20local registrations       = pdfbackend.registrations
21
22local viewerlayers        = attributes.viewerlayers
23
24local references          = structures.references
25
26references.executers      = references.executers or { }
27local executers           = references.executers
28
29local variables           = interfaces.variables
30
31local v_no                = variables.no
32local v_yes               = variables.yes
33local v_start             = variables.start
34local v_stop              = variables.stop
35local v_reset             = variables.reset
36local v_auto              = variables.auto
37local v_random            = variables.random
38
39local lpdf                = lpdf
40local pdfconstant         = lpdf.constant
41local pdfdictionary       = lpdf.dictionary
42local pdfarray            = lpdf.array
43local pdfreference        = lpdf.reference
44local pdfflushobject      = lpdf.flushobject
45local pdfreserveobject    = lpdf.reserveobject
46local addtopageattributes = lpdf.addtopageattributes
47local addtopageresources  = lpdf.addtopageresources
48local addtocatalog        = lpdf.addtocatalog
49
50local escaped             = lpdf.escaped
51
52local nodes               = nodes
53local nuts                = nodes.nuts
54
55local copy_node           = nuts.copy
56
57local nodepool            = nuts.pool
58local register            = nodepool.register
59local setstate            = nodepool.setstate
60
61local pdf_ocg             = pdfconstant("OCG")
62local pdf_ocmd            = pdfconstant("OCMD")
63local pdf_off             = pdfconstant("OFF")
64local pdf_on              = pdfconstant("ON")
65local pdf_view            = pdfconstant("View")
66local pdf_design          = pdfconstant("Design")
67local pdf_toggle          = pdfconstant("Toggle")
68local pdf_setocgstate     = pdfconstant("SetOCGState")
69
70local pdf_print = {
71    [v_yes] = pdfdictionary { PrintState = pdf_on  },
72    [v_no ] = pdfdictionary { PrintState = pdf_off },
73}
74
75local pdf_intent = {
76    [v_yes] = pdf_view,
77    [v_no]  = pdf_design,
78}
79
80local pdf_export = {
81 -- [v_yes] = pdf_on,
82 -- [v_no]  = pdf_off,
83    [v_yes] = pdfdictionary { ExportState = pdf_on },
84    [v_no]  = pdfdictionary { ExportState = pdf_on },
85}
86
87-- We can have references to layers before they are places, for instance from
88-- hide and vide actions. This is why we need to be able to force usage of layers
89-- at several moments.
90
91-- management
92
93local pdfln, pdfld = { }, { }
94local textlayers, hidelayers, videlayers = pdfarray(), pdfarray(), pdfarray()
95local pagelayers, pagelayersreference, cache = nil, nil, { }
96local alphabetic = { }
97
98local escapednames   = table.setmetatableindex(function(t,k)
99--     local v = escaped(k)
100    local v = escaped(k,true)
101    t[k] = v
102    return v
103end)
104
105local specifications = { }
106local initialized    = { }
107
108function codeinjections.defineviewerlayer(specification)
109    if viewerlayers.supported and textlayers then
110        local tag = specification.tag
111        if not specifications[tag] then
112            specifications[tag] = specification
113        end
114    end
115end
116
117function codeinjections.setupviewerlayer(specification)
118    if viewerlayers.supported and textlayers then
119        local tag = specification.tag
120        local set = tag and specifications[tag]
121        if set then
122            for k, v in next, specification do set[k] = v end
123        end
124    end
125end
126
127local function useviewerlayer(name) -- move up so that we can use it as local
128    if not environment.initex and not initialized[name] then
129        local specification = specifications[name]
130        if specification then
131            specifications[name] = nil -- or not
132            initialized   [name] = true
133            if not pagelayers then
134                pagelayers = pdfdictionary()
135                pagelayersreference = pdfreserveobject()
136            end
137            local tag = specification.tag
138            -- todo: reserve
139            local nn = pdfreserveobject()
140            local nr = pdfreference(nn)
141            local nd = pdfdictionary {
142                Type  = pdf_ocg,
143                Name  = specification.title or "unknown",
144                Usage = {
145                    Intent = pdf_intent[specification.editable  or v_yes], -- disable layer hiding by user (useless)
146                    Print  = pdf_print [specification.printable or v_yes], -- printable or not
147                    Export = pdf_export[specification.export    or v_yes], -- export or not
148                },
149            }
150            cache[#cache+1] = { nn, nd }
151            pdfln[tag] = nr -- was n
152            local dn = pdfreserveobject()
153            local dr = pdfreference(dn)
154            local dd = pdfdictionary {
155                Type = pdf_ocmd,
156                OCGs = pdfarray { nr },
157            }
158            cache[#cache+1] = { dn, dd }
159            pdfld[tag] = dr
160            textlayers[#textlayers+1] = nr
161            alphabetic[tag] = nr
162            if specification.visible == v_start then
163                videlayers[#videlayers+1] = nr
164            else
165                hidelayers[#hidelayers+1] = nr
166            end
167         -- pagelayers[escapednames[tag]] = dr -- check
168            pagelayers[tag] = dr -- check
169        else
170            -- todo: message
171        end
172    end
173end
174
175codeinjections.useviewerlayer = useviewerlayer
176
177local function layerreference(name)
178    local r = pdfln[name]
179    if r then
180        return r
181    else
182        useviewerlayer(name)
183        return pdfln[name]
184    end
185end
186
187lpdf.layerreference = layerreference -- also triggered when a hide or vide happens
188
189local function flushtextlayers()
190    if viewerlayers.supported then
191        if pagelayers then
192            pdfflushobject(pagelayersreference,pagelayers)
193        end
194        for i=1,#cache do
195            local ci = cache[i]
196            pdfflushobject(ci[1],ci[2])
197        end
198        if textlayers and #textlayers > 0 then -- we can group them if needed, like: layout
199            local sortedlayers = { }
200            for k, v in table.sortedhash(alphabetic) do
201                sortedlayers[#sortedlayers+1] = v -- maybe do a proper numeric sort as well
202            end
203            local d = pdfdictionary {
204                OCGs = textlayers,
205                D    = pdfdictionary {
206                    Name      = "Document",
207                 -- Order     = (viewerlayers.hasorder and textlayers) or nil,
208                    Order     = (viewerlayers.hasorder and sortedlayers) or nil,
209                    ON        = videlayers,
210                    OFF       = hidelayers,
211                    BaseState = pdf_on,
212                    AS = pdfarray {
213                        pdfdictionary {
214                            Category = pdfarray { pdfconstant("Print") },
215                            Event    = pdfconstant("Print"),
216                            OCGs     = (viewerlayers.hasorder and sortedlayers) or nil,
217                        }
218                    },
219                },
220            }
221            addtocatalog("OCProperties",d)
222            textlayers = nil
223        end
224    end
225end
226
227local function flushpagelayers() -- we can share these
228    if pagelayers then
229        addtopageresources("Properties",pdfreference(pagelayersreference)) -- we could cache this
230    end
231end
232
233lpdf.registerpagefinalizer    (flushpagelayers,"layers")
234lpdf.registerdocumentfinalizer(flushtextlayers,"layers")
235
236local function setlayer(what,arguments)
237    -- maybe just a gmatch of even better, earlier in lpeg
238    arguments = (type(arguments) == "table" and arguments) or settings_to_array(arguments)
239    local state = pdfarray { what }
240    for i=1,#arguments do
241        local p = layerreference(arguments[i])
242        if p then
243            state[#state+1] = p
244        end
245    end
246    return pdfdictionary {
247        S     = pdf_setocgstate,
248        State = state,
249    }
250end
251
252function executers.hidelayer  (arguments) return setlayer(pdf_off,   arguments) end
253function executers.videlayer  (arguments) return setlayer(pdf_on,    arguments) end
254function executers.togglelayer(arguments) return setlayer(pdf_toggle,arguments) end
255
256-- injection
257
258-- local f_bdc = formatters["/OC /%s BDC"]
259local f_bdc = formatters["/OC %s BDC"]
260local s_emc = "EMC"
261
262function codeinjections.startlayer(name) -- used in mp
263    if not name then
264        name = "unknown"
265    end
266    useviewerlayer(name)
267    return f_bdc(escapednames[name])
268end
269
270function codeinjections.stoplayer(name) -- used in mp
271    return s_emc
272end
273
274local cache = { }
275local stop  = nil
276
277function nodeinjections.startlayer(name)
278    local c = cache[name]
279    if not c then
280        useviewerlayer(name)
281        c = register(setstate(f_bdc(escapednames[name])))
282        cache[name] = c
283    end
284    return copy_node(c)
285end
286
287function nodeinjections.stoplayer()
288    if not stop then
289        stop = register(setstate(s_emc))
290    end
291    return copy_node(stop)
292end
293
294-- experimental stacker code (slow, can be optimized): !!!! TEST CODE !!!!
295
296local values     = viewerlayers.values
297local startlayer = codeinjections.startlayer
298local stoplayer  = codeinjections.stoplayer
299
300function nodeinjections.startstackedlayer(s,t,first,last)
301    local r = { }
302    for i=first,last do
303        r[#r+1] = startlayer(values[t[i]])
304    end
305    r = concat(r," ")
306    return setstate(r)
307end
308
309function nodeinjections.stopstackedlayer(s,t,first,last)
310    local r = { }
311    for i=last,first,-1 do
312        r[#r+1] = stoplayer()
313    end
314    r = concat(r," ")
315    return setstate(r)
316end
317
318function nodeinjections.changestackedlayer(s,t1,first1,last1,t2,first2,last2)
319    local r = { }
320    for i=last1,first1,-1 do
321        r[#r+1] = stoplayer()
322    end
323    for i=first2,last2 do
324        r[#r+1] = startlayer(values[t2[i]])
325    end
326    r = concat(r," ")
327    return setstate(r)
328end
329
330-- transitions
331
332local pagetransitions = {
333    {"split","in","vertical"}, {"split","in","horizontal"},
334    {"split","out","vertical"}, {"split","out","horizontal"},
335    {"blinds","horizontal"}, {"blinds","vertical"},
336    {"box","in"}, {"box","out"},
337    {"wipe","east"}, {"wipe","west"}, {"wipe","north"}, {"wipe","south"},
338    {"dissolve"},
339    {"glitter","east"}, {"glitter","south"},
340    {"fly","in","east"}, {"fly","in","west"}, {"fly","in","north"}, {"fly","in","south"},
341    {"fly","out","east"}, {"fly","out","west"}, {"fly","out","north"}, {"fly","out","south"},
342    {"push","east"}, {"push","west"}, {"push","north"}, {"push","south"},
343    {"cover","east"}, {"cover","west"}, {"cover","north"}, {"cover","south"},
344    {"uncover","east"}, {"uncover","west"}, {"uncover","north"}, {"uncover","south"},
345    {"fade"},
346}
347
348local mapping = {
349    split      = { "S"  , pdfconstant("Split") },
350    blinds     = { "S"  , pdfconstant("Blinds") },
351    box        = { "S"  , pdfconstant("Box") },
352    wipe       = { "S"  , pdfconstant("Wipe") },
353    dissolve   = { "S"  , pdfconstant("Dissolve") },
354    glitter    = { "S"  , pdfconstant("Glitter") },
355    replace    = { "S"  , pdfconstant("R") },
356    fly        = { "S"  , pdfconstant("Fly") },
357    push       = { "S"  , pdfconstant("Push") },
358    cover      = { "S"  , pdfconstant("Cover") },
359    uncover    = { "S"  , pdfconstant("Uncover") },
360    fade       = { "S"  , pdfconstant("Fade") },
361    horizontal = { "Dm" , pdfconstant("H") },
362    vertical   = { "Dm" , pdfconstant("V") },
363    ["in"]     = { "M"  , pdfconstant("I") },
364    out        = { "M"  , pdfconstant("O") },
365    east       = { "Di" ,   0 },
366    north      = { "Di" ,  90 },
367    west       = { "Di" , 180 },
368    south      = { "Di" , 270 },
369}
370
371local last = 0
372
373-- n: number, "stop", "reset", "random", "a,b,c" delay: number, "none"
374
375function codeinjections.setpagetransition(specification)
376    local n, delay = specification.n, specification.delay
377    if not n or n == "" then
378        return -- let's forget about it
379    elseif n == v_auto then
380        if last >= #pagetransitions then
381            last = 0
382        end
383        n = last + 1
384    elseif n == v_stop then
385        return
386    elseif n == v_reset then
387        last = 0
388        return
389    elseif n == v_random then
390        n = getrandom("transition",1,#pagetransitions)
391    else
392        n = tonumber(n)
393    end
394    local t = n and pagetransitions[n] or pagetransitions[1]
395    if not t then
396        t = settings_to_array(n)
397    end
398    if t and #t > 0 then
399        local d = pdfdictionary()
400        for i=1,#t do
401            local m = mapping[t[i]]
402            d[m[1]] = m[2]
403        end
404        delay = tonumber(delay)
405        if delay and delay > 0 then
406            addtopageattributes("Dur",delay)
407        end
408        addtopageattributes("Trans",d)
409    end
410end
411