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
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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
87
88local presets = { }
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
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
224
225local nofattachments = 0
226local attachments = { }
227local filestreams = { }
228local referenced = { }
229local ignorereferenced = true
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
266 f[#f+1] = reference
267 else
268
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
276 lpdf.addtocatalog("AF", pdfreference(pdfflushobject(f)))
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
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
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)
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
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
366 UF = pdfunicode(savename),
367 EF = pdfdictionary { F = pdfreference(f) },
368 Desc = title ~= "" and pdfunicode(title) or nil,
369 AFRelationship = pdfconstant("Unspecified"),
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
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
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
458 F = bit32.band(bit32.bor(flags,4),(1023-1-2-32-256)),
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)
473 return filestreams[filename]
474end
475
476
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
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)
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 ""
521 local title = specification.title or ""
522 local subtitle = specification.subtitle or ""
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
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
572 box.height = height
573 box.depth = depth
574 return box
575end
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
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
624
625 if specification.option == v_auto then
626 if openpageaction then
627
628 end
629 if closepageaction then
630
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")
641 local r = mu[label] or pdfreserveobject()
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,
653 T = pdfunicode(label),
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))
661 return pdfreference(r)
662end
663
664
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
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697 local parameters = pdfdictionary {
698 Type = pdfconstant("MediaPermissions"),
699 TF = pdfstring("TEMPALWAYS"),
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,
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" },
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)
736 local label = specification.label
737 if not mf[label] then
738 report_media("unknown medium, label %a",label)
739 local clip = pdfdictionary {
740 Type = pdfconstant("MediaClip"),
741 S = pdfconstant("MCD"),
742 N = label,
743 D = pdfreference(unknown),
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
759 elseif specification.type == "external" then
760 insertrendering(specification)
761 else
762 insertrenderingobject(specification)
763 end
764end
765
766
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
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 |