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