lpdf-wid.lua /size: 28 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['lpdf-wid'] = {
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-- It's about time to give up on media in pdf and admit that pdf lost it to html.
10-- First we had movies and sound, quite easy to deal with, but obsolete now. Then we
11-- had renditions but they turned out to be unreliable from the start and look
12-- obsolete too or at least they are bound to the (obsolete) flash technology for
13-- rendering. They were already complex constructs. Now we have rich media which
14-- instead of providing a robust future proof framework for general media types
15-- again seems to depend on viewers built in (yes, also kind of obsolete) flash
16-- technology, and we cannot expect this non-open technology to show up in open
17-- browsers. So, in the end we can best just use links to external resources to be
18-- future proof. Just look at the viewer preferences pane to see how fragile support
19-- is. Interestingly u3d support is kind of built in, while e.g. mp4 support relies
20-- on wrapping in swf. We used to stay ahead of the pack with support of the fancy
21-- pdf features but it backfires and is not worth the trouble. And yes, for control
22-- (even simple like starting and stopping videos) one has to revert to JavaScript,
23-- the other fragile bit. And, now that adobe quits flash in 2020 we're without any
24-- video anyway. Also, it won't play on all platforms and devices so let's wait for
25-- html5 media in pdf then.
26--
27-- See mail by Michal Vlasák to the mailing list that discusses current support in
28-- viewers and also mentions (and submitted) a few fixes wrt embedding media.
29
30local tonumber, next = tonumber, next
31local gmatch, gsub, find, lower = string.gmatch, string.gsub, string.find, string.lower
32local filenameonly, basefilename, filesuffix, addfilesuffix = file.nameonly, file.basename, file.suffix, file.addsuffix
33local isfile, modificationtime = lfs.isfile, lfs.modification
34local stripstring = string.strip
35local settings_to_array = utilities.parsers.settings_to_array
36local settings_to_hash = utilities.parsers.settings_to_hash
37local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys
38
39local report_media             = logs.reporter("backend","media")
40local report_attachment        = logs.reporter("backend","attachment")
41
42local backends                 = backends
43local lpdf                     = lpdf
44local nodes                    = nodes
45local context                  = context
46
47local texgetcount              = tex.getcount
48
49local hpacknode                = nodes.hpack
50
51local nodeinjections           = backends.pdf.nodeinjections
52local codeinjections           = backends.pdf.codeinjections
53local registrations            = backends.pdf.registrations
54
55local executers                = structures.references.executers
56local variables                = interfaces.variables
57
58local v_hidden                 = variables.hidden
59local v_auto                   = variables.auto
60local v_embed                  = variables.embed
61local v_max                    = variables.max
62local v_yes                    = variables.yes
63local v_compress               = variables.compress
64
65local pdfconstant              = lpdf.constant
66local pdfnull                  = lpdf.null
67local pdfdictionary            = lpdf.dictionary
68local pdfarray                 = lpdf.array
69local pdfreference             = lpdf.reference
70local pdfunicode               = lpdf.unicode
71local pdfstring                = lpdf.string
72local pdfboolean               = lpdf.boolean
73local pdfaction                = lpdf.action
74local pdfborder                = lpdf.border
75
76local pdftransparencyvalue     = lpdf.transparencyvalue
77local pdfcolorvalues           = lpdf.colorvalues
78
79local pdfflushobject           = lpdf.flushobject
80local pdfflushstreamobject     = lpdf.flushstreamobject
81local pdfflushstreamfileobject = lpdf.flushstreamfileobject
82local pdfreserveobject         = lpdf.reserveobject
83local pdfpagereference         = lpdf.pagereference
84local pdfshareobjectreference  = lpdf.shareobjectreference
85
86-- symbols
87
88local presets = { } -- xforms
89
90local function registersymbol(name,n)
91    presets[name] = pdfreference(n)
92end
93
94local function registeredsymbol(name)
95    return presets[name]
96end
97
98local function presetsymbol(symbol)
99    if not presets[symbol] then
100        context.predefinesymbol { symbol }
101    end
102end
103
104local function presetsymbollist(list)
105    if list then
106        for symbol in gmatch(list,"[^, ]+") do
107            presetsymbol(symbol)
108        end
109    end
110end
111
112codeinjections.registersymbol   = registersymbol
113codeinjections.registeredsymbol = registeredsymbol
114codeinjections.presetsymbol     = presetsymbol
115codeinjections.presetsymbollist = presetsymbollist
116
117-- comments
118
119-- local symbols = {
120--     Addition     = pdfconstant("NewParagraph"),
121--     Attachment   = pdfconstant("Attachment"),
122--     Balloon      = pdfconstant("Comment"),
123--     Check        = pdfconstant("Check Mark"),
124--     CheckMark    = pdfconstant("Check Mark"),
125--     Circle       = pdfconstant("Circle"),
126--     Cross        = pdfconstant("Cross"),
127--     CrossHairs   = pdfconstant("Cross Hairs"),
128--     Graph        = pdfconstant("Graph"),
129--     InsertText   = pdfconstant("Insert Text"),
130--     New          = pdfconstant("Insert"),
131--     Paperclip    = pdfconstant("Paperclip"),
132--     RightArrow   = pdfconstant("Right Arrow"),
133--     RightPointer = pdfconstant("Right Pointer"),
134--     Star         = pdfconstant("Star"),
135--     Tag          = pdfconstant("Tag"),
136--     Text         = pdfconstant("Note"),
137--     TextNote     = pdfconstant("Text Note"),
138--     UpArrow      = pdfconstant("Up Arrow"),
139--     UpLeftArrow  = pdfconstant("Up-Left Arrow"),
140-- }
141
142local attachment_symbols = {
143    Graph     = pdfconstant("Graph"),
144    Paperclip = pdfconstant("Paperclip"),
145    Pushpin   = pdfconstant("PushPin"),
146}
147
148attachment_symbols.PushPin = attachment_symbols.Pushpin
149attachment_symbols.Default = attachment_symbols.Pushpin
150
151function lpdf.attachmentsymbols()
152    return sortedkeys(comment_symbols)
153end
154
155local comment_symbols = {
156    Comment      = pdfconstant("Comment"),
157    Help         = pdfconstant("Help"),
158    Insert       = pdfconstant("Insert"),
159    Key          = pdfconstant("Key"),
160    Newparagraph = pdfconstant("NewParagraph"),
161    Note         = pdfconstant("Note"),
162    Paragraph    = pdfconstant("Paragraph"),
163}
164
165comment_symbols.NewParagraph = Newparagraph
166comment_symbols.Default      = Note
167
168function lpdf.commentsymbols()
169    return sortedkeys(comment_symbols)
170end
171
172local function analyzesymbol(symbol,collection)
173    if not symbol or symbol == "" then
174        return collection and collection.Default, nil
175    elseif collection and collection[symbol] then
176        return collection[symbol], nil
177    else
178        local setn, setr, setd
179        local set = settings_to_array(symbol)
180        if #set == 1 then
181            setn, setr, setd = set[1], set[1], set[1]
182        elseif #set == 2 then
183            setn, setr, setd = set[1], set[1], set[2]
184        else
185            setn, setr, setd = set[1], set[2], set[3]
186        end
187        local appearance = pdfdictionary {
188            N = setn and registeredsymbol(setn),
189            R = setr and registeredsymbol(setr),
190            D = setd and registeredsymbol(setd),
191        }
192        local appearanceref = pdfshareobjectreference(appearance)
193        return nil, appearanceref
194    end
195end
196
197local function analyzenormalsymbol(symbol)
198    local appearance = pdfdictionary {
199        N = registeredsymbol(symbol),
200    }
201    local appearanceref = pdfshareobjectreference(appearance)
202    return appearanceref
203end
204
205codeinjections.analyzesymbol       = analyzesymbol
206codeinjections.analyzenormalsymbol = analyzenormalsymbol
207
208local function analyzelayer(layer)
209    -- todo:  (specification.layer ~= "" and pdfreference(specification.layer)) or nil, -- todo: ref to layer
210end
211
212local function analyzecolor(colorvalue,colormodel)
213    local cvalue = colorvalue and tonumber(colorvalue)
214    local cmodel = colormodel and tonumber(colormodel) or 3
215    return cvalue and pdfarray { pdfcolorvalues(cmodel,cvalue) } or nil
216end
217
218local function analyzetransparency(transparencyvalue)
219    local tvalue = transparencyvalue and tonumber(transparencyvalue)
220    return tvalue and pdftransparencyvalue(tvalue) or nil
221end
222
223-- Attachments
224
225local nofattachments    = 0
226local attachments       = { }
227local filestreams       = { }
228local referenced        = { }
229local ignorereferenced  = true -- fuzzy pdf spec .. twice in attachment list, can become an option
230local tobesavedobjrefs  = utilities.storage.allocate()
231local collectedobjrefs  = utilities.storage.allocate()
232local permitted         = true
233local enabled           = true
234
235function codeinjections.setattachmentsupport(option)
236    if option == false then
237        permitted = false
238        enabled   = false
239    end
240end
241
242local fileobjreferences = {
243    collected = collectedobjrefs,
244    tobesaved = tobesavedobjrefs,
245}
246
247job.fileobjreferences = fileobjreferences
248
249local function initializer()
250    collectedobjrefs = job.fileobjreferences.collected or { }
251    tobesavedobjrefs = job.fileobjreferences.tobesaved or { }
252end
253
254job.register('job.fileobjreferences.collected', tobesavedobjrefs, initializer)
255
256local function flushembeddedfiles()
257    if enabled and next(filestreams) then
258        local e = pdfarray()
259        local f = pdfarray()
260        for tag, reference in sortedhash(filestreams) do
261            if not reference then
262                report_attachment("unreferenced file, tag %a",tag)
263            elseif referenced[tag] == "hidden" or referenced[tag] == "forced" then
264                e[#e+1] = pdfstring(tag)
265                e[#e+1] = reference -- already a reference
266                f[#f+1] = reference -- collect all file description references
267            else
268                -- messy spec ... when annot not in named else twice in menu list acrobat
269                f[#f+1] = reference
270            end
271        end
272        if #e > 0 then
273            lpdf.addtonames("EmbeddedFiles",pdfreference(pdfflushobject(pdfdictionary{ Names = e })))
274        end
275        if #f > 0 then -- PDF/A-2|3: all associated files must have a relationship to the PDF document (global or part)
276            lpdf.addtocatalog("AF", pdfreference(pdfflushobject(f))) -- global (Catalog)
277        end
278    end
279end
280
281lpdf.registerdocumentfinalizer(flushembeddedfiles,"embeddedfiles")
282
283function codeinjections.embedfile(specification)
284    if enabled then
285        local data      = specification.data
286        local filename  = specification.file
287        local name      = specification.name or ""
288        local title     = specification.title or ""
289        local hash      = specification.hash or filename
290        local keepdir   = specification.keepdir -- can change
291        local usedname  = specification.usedname
292        local filetype  = specification.filetype
293        local compress  = specification.compress
294        local mimetype  = specification.mimetype or specification.mime
295        if filename == "" then
296            filename = nil
297        end
298        if compress == nil then
299            compress = true
300        end
301        if data then
302            local r = filestreams[hash]
303            if r == false then
304                return nil
305            elseif r then
306                return r
307            elseif not filename then
308                filename = specification.tag
309                if not filename or filename == "" then
310                    filename = specification.registered
311                end
312                if not filename or filename == "" then
313                    filename = hash
314                end
315            end
316        else
317            if not filename then
318                return nil
319            end
320            local r = filestreams[hash]
321            if r == false then
322                return nil
323            elseif r then
324                return r
325            else
326                local foundname = resolvers.findbinfile(filename) or ""
327                if foundname == "" or not isfile(foundname) then
328                    filestreams[filename] = false
329                    return nil
330                else
331                    specification.foundname = foundname
332                end
333            end
334        end
335        -- needs to be cleaned up:
336        usedname = usedname ~= "" and usedname or filename or name
337        local basename  = keepdir == true and usedname or basefilename(usedname)
338        local basename  = gsub(basename,"%./","")
339        local savename  = name ~= "" and name or basename
340        local foundname = specification.foundname or filename
341        if not filetype or filetype == "" then
342            filetype = name and (filename and filesuffix(filename)) or "txt"
343        end
344        savename = addfilesuffix(savename,filetype) -- type is mandate for proper working in viewer
345        local a = pdfdictionary {
346            Type    = pdfconstant("EmbeddedFile"),
347            Subtype = mimetype and mimetype ~= "" and pdfconstant(mimetype) or nil,
348        }
349        local f
350        if data then
351            f = pdfflushstreamobject(data,a)
352            specification.data = true -- signal that still data but already flushed
353        else
354            local attributes   = lfs.attributes(foundname)
355            local modification = modificationtime(foundname)
356            a.Params = {
357                Size    = attributes.size,
358                ModDate = lpdf.pdftimestamp(modification),
359            }
360            f = pdfflushstreamfileobject(foundname,a,compress)
361        end
362        local d = pdfdictionary {
363            Type           = pdfconstant("Filespec"),
364            F              = pdfstring(savename),
365         -- UF             = pdfstring(savename),
366            UF             = pdfunicode(savename),
367            EF             = pdfdictionary { F = pdfreference(f) },
368            Desc           = title ~= "" and pdfunicode(title) or nil,
369            AFRelationship = pdfconstant("Unspecified"), -- Supplement, Data, Source, Alternative, Data
370        }
371        local r = pdfreference(pdfflushobject(d))
372        filestreams[hash] = r
373        if specification.forcereference == true then
374            referenced[hash] = "forced"
375        end
376        return r
377    end
378end
379
380function nodeinjections.attachfile(specification)
381    if enabled then
382        local registered = specification.registered or "<unset>"
383        local data = specification.data
384        local hash
385        local filename
386        if data then
387            hash = md5.HEX(data)
388        else
389            filename = specification.file
390            if not filename or filename == "" then
391                report_attachment("no file specified, using registered %a instead",registered)
392                filename = registered
393                specification.file = registered
394            end
395            local foundname = resolvers.findbinfile(filename) or ""
396            if foundname == "" or not isfile(foundname) then
397                report_attachment("invalid filename %a, ignoring registered %a",filename,registered)
398                return nil
399            else
400                specification.foundname = foundname
401            end
402            hash = filename
403        end
404        specification.hash = hash
405        nofattachments = nofattachments + 1
406        local registered = specification.registered or ""
407        local title      = specification.title      or ""
408        local subtitle   = specification.subtitle   or ""
409        local author     = specification.author     or ""
410        local onlyname   = filename and filenameonly(filename) or ""
411        if registered == "" then
412            registered = filename
413        end
414        if author == "" and title ~= "" then
415            author = title
416            title  = onlyname or ""
417        end
418        if author == "" then
419            author = onlyname or "<unknown>"
420        end
421        if title == "" then
422            title = registered
423        end
424        if title == "" and filename then
425            title = onlyname
426        end
427        local aref = attachments[registered]
428        if not aref then
429            aref = codeinjections.embedfile(specification)
430            attachments[registered] = aref
431        end
432        local reference = specification.reference
433        if reference and aref then
434            tobesavedobjrefs[reference] = aref[1]
435        end
436        if not aref then
437            report_attachment("skipping attachment, registered %a",registered)
438            -- already reported
439        elseif specification.method == v_hidden then
440            referenced[hash] = "hidden"
441        else
442            referenced[hash] = "annotation"
443            local name, appearance = analyzesymbol(specification.symbol,attachment_symbols)
444            local flags = specification.flags or 0 -- to keep it expandable
445            local d = pdfdictionary {
446                Subtype  = pdfconstant("FileAttachment"),
447                FS       = aref,
448                Contents = pdfunicode(title),
449                Name     = name,
450                NM       = pdfstring("attachment:"..nofattachments),
451                T        = author ~= "" and pdfunicode(author) or nil,
452                Subj     = subtitle ~= "" and pdfunicode(subtitle) or nil,
453                C        = analyzecolor(specification.colorvalue,specification.colormodel),
454                CA       = analyzetransparency(specification.transparencyvalue),
455                AP       = appearance,
456                OC       = analyzelayer(specification.layer),
457             -- F        = pdfnull(), -- another rediculous need to satisfy validation
458                F        = bit32.band(bit32.bor(flags,4),(1023-1-2-32-256)), -- set 3, clear 1,2,6,9; PDF 32000-1, p385
459            }
460            local width  = specification.width  or 0
461            local height = specification.height or 0
462            local depth  = specification.depth  or 0
463            local box    = hpacknode(nodeinjections.annotation(width,height,depth,d()))
464            box.width    = width
465            box.height   = height
466            box.depth    = depth
467            return box
468        end
469    end
470end
471
472function codeinjections.attachmentid(filename) -- not used in context
473    return filestreams[filename]
474end
475
476-- Comments
477
478local nofcomments      = 0
479local usepopupcomments = false
480
481local defaultattributes = {
482    ["xmlns"]           = "http://www.w3.org/1999/xhtml",
483    ["xmlns:xfa"]       = "http://www.xfa.org/schema/xfa-data/1.0/",
484    ["xfa:contentType"] = "text/html",
485    ["xfa:APIVersion"]  = "Acrobat:8.0.0",
486    ["xfa:spec"]        = "2.4",
487}
488
489local function checkcontent(text,option)
490    if option and option.xml then
491        local root = xml.convert(text)
492        if root and not root.er then
493            xml.checkbom(root)
494            local body = xml.first(root,"/body")
495            if body then
496                local at = body.at
497                for k, v in next, defaultattributes do
498                    if not at[k] then
499                        at[k] = v
500                    end
501                end
502             -- local content = xml.textonly(root)
503                local richcontent = xml.tostring(root)
504                return nil, pdfunicode(richcontent)
505            end
506        end
507    end
508    return pdfunicode(text)
509end
510
511function nodeinjections.comment(specification) -- brrr: seems to be done twice
512    nofcomments = nofcomments + 1
513    local text = specification.data or ""
514    if specification.space ~= v_yes then
515        text = stripstring(text)
516        text = gsub(text,"[\n\r] *","\n")
517    end
518    text = gsub(text,"\r","\n")
519    local name, appearance = analyzesymbol(specification.symbol,comment_symbols)
520    local tag      = specification.tag      or "" -- this is somewhat messy as recent
521    local title    = specification.title    or "" -- versions of acrobat see the title
522    local subtitle = specification.subtitle or "" -- as author
523    local author   = specification.author   or ""
524    local option   = settings_to_hash(specification.option or "")
525    if author ~= "" then
526        if subtitle == "" then
527            subtitle = title
528        elseif title ~= "" then
529            subtitle = subtitle .. ", " .. title
530        end
531        title = author
532    end
533    if title == "" then
534        title = tag
535    end
536    local content, richcontent = checkcontent(text,option)
537    local d = pdfdictionary {
538        Subtype   = pdfconstant("Text"),
539        Open      = option[v_max] and pdfboolean(true) or nil,
540        Contents  = content,
541        RC        = richcontent,
542        T         = title ~= "" and pdfunicode(title) or nil,
543        Subj      = subtitle ~= "" and pdfunicode(subtitle) or nil,
544        C         = analyzecolor(specification.colorvalue,specification.colormodel),
545        CA        = analyzetransparency(specification.transparencyvalue),
546        OC        = analyzelayer(specification.layer),
547        Name      = name,
548        NM        = pdfstring("comment:"..nofcomments),
549        AP        = appearance,
550    }
551    local width  = specification.width  or 0
552    local height = specification.height or 0
553    local depth  = specification.depth  or 0
554    local box
555    if usepopupcomments then
556        -- rather useless as we can hide/vide
557        local nd = pdfreserveobject()
558        local nc = pdfreserveobject()
559        local c = pdfdictionary {
560            Subtype = pdfconstant("Popup"),
561            Parent  = pdfreference(nd),
562        }
563        d.Popup = pdfreference(nc)
564        box = hpacknode(
565            nodeinjections.annotation(0,0,0,d(),nd),
566            nodeinjections.annotation(width,height,depth,c(),nc)
567        )
568    else
569        box = hpacknode(nodeinjections.annotation(width,height,depth,d()))
570    end
571    box.width  = width  -- redundant
572    box.height = height -- redundant
573    box.depth  = depth  -- redundant
574    return box
575end
576
577-- rendering stuff
578--
579-- object_1  -> <</Type /Rendition /S /MR /C << /Type /MediaClip ... >> >>
580-- object_2  -> <</Type /Rendition /S /MR /C << /Type /MediaClip ... >> >>
581-- rendering -> <</Type /Rendition /S /MS [objref_1 objref_2]>>
582--
583-- we only work foreward here (currently)
584-- annotation is to be packed at the tex end
585
586-- aiff audio/aiff
587-- au   audio/basic
588-- avi  video/avi
589-- mid  audio/midi
590-- mov  video/quicktime
591-- mp3  audio/x-mp3 (mpeg)
592-- mp4  audio/mp4
593-- mp4  video/mp4
594-- mpeg video/mpeg
595-- smil application/smil
596-- swf  application/x-shockwave-flash
597
598-- P  media play parameters (evt /BE for controls etc
599-- A  boolean (audio)
600-- C  boolean (captions)
601-- O  boolean (overdubs)
602-- S  boolean (subtitles)
603-- PL pdfconstant("ADBE_MCI"),
604
605-- F        = flags,
606-- T        = title,
607-- Contents = rubish,
608-- AP       = irrelevant,
609
610-- sound is different, no window (or zero) so we need to collect them and
611-- force them if not set
612
613local ms, mu, mf = { }, { }, { }
614
615local function delayed(label)
616    local a = pdfreserveobject()
617    mu[label] = a
618    return pdfreference(a)
619end
620
621local function insertrenderingwindow(specification)
622    local label = specification.label
623 -- local openpage = specification.openpage
624 -- local closepage = specification.closepage
625    if specification.option == v_auto then
626        if openpageaction then
627            -- \handlereferenceactions{\v!StartRendering{#2}}
628        end
629        if closepageaction then
630            -- \handlereferenceactions{\v!StopRendering {#2}}
631        end
632    end
633    local actions = nil
634    if openpage or closepage then
635        actions = pdfdictionary {
636            PO = (openpage  and lpdfaction(openpage )) or nil,
637            PC = (closepage and lpdfaction(closepage)) or nil,
638        }
639    end
640    local page = tonumber(specification.page) or texgetcount("realpageno") -- todo
641    local r = mu[label] or pdfreserveobject() -- why the reserve here?
642    local a = pdfdictionary {
643        S  = pdfconstant("Rendition"),
644        R  = mf[label],
645        OP = 0,
646        AN = pdfreference(r),
647    }
648    local bs, bc = pdfborder()
649    local d = pdfdictionary {
650        Subtype = pdfconstant("Screen"),
651        P       = pdfreference(pdfpagereference(page)),
652        A       = a, -- needed in order to make the annotation clickable (i.e. don't bark)
653        T       = pdfunicode(label), -- for JS
654        Border  = bs,
655        C       = bc,
656        AA      = actions,
657    }
658    local width = specification.width or 0
659    local height = specification.height or 0
660    context(nodeinjections.annotation(width,height,0,d(),r)) -- save ref
661    return pdfreference(r)
662end
663
664-- some dictionaries can have a MH (must honor) or BE (best effort) capsule
665
666local function insertrendering(specification)
667    local label  = specification.label
668    local option = settings_to_hash(specification.option)
669    if not mf[label] then
670        local filename = specification.filename
671        local isurl    = find(filename,"://",1,true)
672        local mimetype = specification.mimetype or specification.mime
673     -- local start = pdfdictionary {
674     --     Type = pdfconstant("MediaOffset"),
675     --     S = pdfconstant("T"), -- time
676     --     T = pdfdictionary { -- time
677     --         Type = pdfconstant("Timespan"),
678     --         S    = pdfconstant("S"),
679     --         V    = 3, -- time in seconds
680     --     },
681     -- }
682     -- local start = pdfdictionary {
683     --     Type = pdfconstant("MediaOffset"),
684     --     S = pdfconstant("F"), -- frame
685     --     F = 100 -- framenumber
686     -- }
687     -- local start = pdfdictionary {
688     --     Type = pdfconstant("MediaOffset"),
689     --     S = pdfconstant("M"), -- mark
690     --     M = "somemark",
691     -- }
692     -- local parameters = pdfdictionary {
693     --     BE = pdfdictionary {
694     --          B = start,
695     --     }
696     -- }
697        local parameters = pdfdictionary {
698            Type = pdfconstant("MediaPermissions"),
699            TF   = pdfstring("TEMPALWAYS"), -- TEMPNEVER TEMPEXTRACT TEMPACCESS TEMPALWAYS / needed for acrobat/wmp
700        }
701        local descriptor = pdfdictionary {
702            Type = pdfconstant("Filespec"),
703            F    = filename,
704        }
705        if isurl then
706            descriptor.FS = pdfconstant("URL")
707            descriptor = pdfreference(pdfflushobject(descriptor))
708        elseif option[v_embed] then
709            descriptor = codeinjections.embedfile {
710                file           = filename,
711                mimetype       = mimetype, -- yes or no
712                compress       = option[v_compress] or false,
713                forcereference = true,
714            }
715        end
716        local clip = pdfdictionary {
717            Type = pdfconstant("MediaClip"),
718            S    = pdfconstant("MCD"),
719            N    = label,
720            CT   = mimetype,
721            Alt  = pdfarray { "", "file not found" }, -- language id + message
722            D    = descriptor,
723            P    = pdfreference(pdfflushobject(parameters)),
724        }
725        local rendition = pdfdictionary {
726            Type = pdfconstant("Rendition"),
727            S    = pdfconstant("MR"),
728            N    = pdfunicode(label),
729            C    = pdfreference(pdfflushobject(clip)),
730        }
731        mf[label] = pdfreference(pdfflushobject(rendition))
732    end
733end
734
735local function insertrenderingobject(specification) -- todo
736    local label = specification.label
737    if not mf[label] then
738        report_media("unknown medium, label %a",label)
739        local clip = pdfdictionary { -- does  not work that well one level up
740            Type = pdfconstant("MediaClip"),
741            S    = pdfconstant("MCD"),
742            N    = label,
743            D    = pdfreference(unknown), -- not label but objectname, hm .. todo?
744        }
745        local rendition = pdfdictionary {
746            Type = pdfconstant("Rendition"),
747            S    = pdfconstant("MR"),
748            N    = label,
749            C    = pdfreference(pdfflushobject(clip)),
750        }
751        mf[label] = pdfreference(pdfflushobject(rendition))
752    end
753end
754
755function codeinjections.processrendering(label)
756    local specification = interactions.renderings.rendering(label)
757    if not specification then
758        -- error
759    elseif specification.type == "external" then
760        insertrendering(specification)
761    else
762        insertrenderingobject(specification)
763    end
764end
765
766-- needed mapping for access from JS
767
768local function flushrenderings()
769    if next(mf) then
770        local r = pdfarray()
771        for label, reference in sortedhash(mf) do
772            r[#r+1] = pdfunicode(label)
773            r[#r+1] = reference -- already a reference
774        end
775        lpdf.addtonames("Renditions",pdfreference(pdfflushobject(pdfdictionary{ Names = r })))
776    end
777end
778
779lpdf.registerdocumentfinalizer(flushrenderings,"renderings")
780
781function codeinjections.insertrenderingwindow(specification)
782    local label = specification.label
783    codeinjections.processrendering(label)
784    ms[label] = insertrenderingwindow(specification)
785end
786
787local function set(operation,arguments)
788    codeinjections.processrendering(arguments)
789    return pdfdictionary {
790        S  = pdfconstant("Rendition"),
791        OP = operation,
792        R  = mf[arguments],
793        AN = ms[arguments] or delayed(arguments),
794    }
795end
796
797function executers.startrendering (arguments) return set(0,arguments) end
798function executers.stoprendering  (arguments) return set(1,arguments) end
799function executers.pauserendering (arguments) return set(2,arguments) end
800function executers.resumerendering(arguments) return set(3,arguments) end
801