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
30
31local tonumber, next = tonumber, next
32local gmatch, gsub, find, lower = string.gmatch, string.gsub, string.find, string.lower
33local filenameonly, basefilename, filesuffix, addfilesuffix = file.nameonly, file.basename, file.suffix, file.addsuffix
34local isfile, modificationtime = lfs.isfile, lfs.modification
35local stripstring = string.strip
36local settings_to_array = utilities.parsers.settings_to_array
37local settings_to_hash = utilities.parsers.settings_to_hash
38local sortedhash, sortedkeys = table.sortedhash, table.sortedkeys
39
40local report_media = logs.reporter("backend","media")
41local report_attachment = logs.reporter("backend","attachment")
42
43local context = context
44
45local texgetcount = tex.getcount
46local getmacro = tokens.getters.macro
47
48local hpacknode = nodes.hpack
49
50local pdfbackend = backends.registered.pdf
51local nodeinjections = pdfbackend.nodeinjections
52local codeinjections = pdfbackend.codeinjections
53local registrations = pdfbackend.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_no = variables.no
64local v_compress = variables.compress
65local v_list = variables.list
66local v_title = variables.title
67
68local lpdf = lpdf
69
70local pdfconstant = lpdf.constant
71local pdfnull = lpdf.null
72local pdfdictionary = lpdf.dictionary
73local pdfarray = lpdf.array
74local pdfreference = lpdf.reference
75local pdfunicode = lpdf.unicode
76local pdfstring = lpdf.string
77local pdfboolean = lpdf.boolean
78local pdfaction = lpdf.action
79local pdfborder = lpdf.border
80
81local pdftransparencyvalue = lpdf.transparencyvalue
82local pdfcolorvalues = lpdf.colorvalues
83
84local pdfflushobject = lpdf.flushobject
85local pdfflushstreamobject = lpdf.flushstreamobject
86local pdfflushstreamfileobject = lpdf.flushstreamfileobject
87local pdfreserveobject = lpdf.reserveobject
88local pdfpagereference = lpdf.pagereference
89local pdfshareobjectreference = lpdf.shareobjectreference
90
91
92
93local presets = { }
94
95local function registersymbol(name,n)
96 presets[name] = pdfreference(n)
97end
98
99local function registeredsymbol(name)
100 return presets[name]
101end
102
103local function presetsymbol(symbol)
104 if not presets[symbol] then
105 context.predefinesymbol { symbol }
106 end
107end
108
109local function presetsymbollist(list)
110 if list then
111 for symbol in gmatch(list,"[^, ]+") do
112 presetsymbol(symbol)
113 end
114 end
115end
116
117codeinjections.registersymbol = registersymbol
118codeinjections.registeredsymbol = registeredsymbol
119codeinjections.presetsymbol = presetsymbol
120codeinjections.presetsymbollist = presetsymbollist
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
147local attachment_symbols = {
148 Graph = pdfconstant("Graph"),
149 Paperclip = pdfconstant("Paperclip"),
150 Pushpin = pdfconstant("PushPin"),
151}
152
153attachment_symbols.PushPin = attachment_symbols.Pushpin
154attachment_symbols.Default = attachment_symbols.Pushpin
155
156function lpdf.attachmentsymbols()
157 return sortedkeys(comment_symbols)
158end
159
160local comment_symbols = {
161 Comment = pdfconstant("Comment"),
162 Help = pdfconstant("Help"),
163 Insert = pdfconstant("Insert"),
164 Key = pdfconstant("Key"),
165 Newparagraph = pdfconstant("NewParagraph"),
166 Note = pdfconstant("Note"),
167 Paragraph = pdfconstant("Paragraph"),
168}
169
170comment_symbols.NewParagraph = Newparagraph
171comment_symbols.Default = Note
172
173function lpdf.commentsymbols()
174 return sortedkeys(comment_symbols)
175end
176
177local function analyzesymbol(symbol,collection)
178 if not symbol or symbol == "" then
179 return collection and collection.Default, nil
180 elseif collection and collection[symbol] then
181 return collection[symbol], nil
182 else
183 local setn, setr, setd
184 local set = settings_to_array(symbol)
185 if #set == 1 then
186 setn, setr, setd = set[1], set[1], set[1]
187 elseif #set == 2 then
188 setn, setr, setd = set[1], set[1], set[2]
189 else
190 setn, setr, setd = set[1], set[2], set[3]
191 end
192 local appearance = pdfdictionary {
193 N = setn and registeredsymbol(setn),
194 R = setr and registeredsymbol(setr),
195 D = setd and registeredsymbol(setd),
196 }
197 local appearanceref = pdfshareobjectreference(appearance)
198 return nil, appearanceref
199 end
200end
201
202local function analyzenormalsymbol(symbol)
203 local appearance = pdfdictionary {
204 N = registeredsymbol(symbol),
205 }
206 local appearanceref = pdfshareobjectreference(appearance)
207 return appearanceref
208end
209
210codeinjections.analyzesymbol = analyzesymbol
211codeinjections.analyzenormalsymbol = analyzenormalsymbol
212
213local function analyzelayer(layer)
214
215end
216
217local function analyzecolor(colorvalue,colormodel)
218 local cvalue = colorvalue and tonumber(colorvalue)
219 local cmodel = colormodel and tonumber(colormodel) or 3
220 return cvalue and pdfarray { pdfcolorvalues(cmodel,cvalue) } or nil
221end
222
223local function analyzetransparency(transparencyvalue)
224 local tvalue = transparencyvalue and tonumber(transparencyvalue)
225 return tvalue and pdftransparencyvalue(tvalue) or nil
226end
227
228
229
230local nofattachments = 0
231local attachments = { }
232local filestreams = { }
233local referenced = { }
234local ignorereferenced = true
235local tobesavedobjrefs = utilities.storage.allocate()
236local collectedobjrefs = utilities.storage.allocate()
237local permitted = true
238local enabled = true
239
240function codeinjections.setattachmentsupport(option)
241 if option == false then
242 permitted = false
243 enabled = false
244 end
245end
246
247local fileobjreferences = {
248 collected = collectedobjrefs,
249 tobesaved = tobesavedobjrefs,
250}
251
252job.fileobjreferences = fileobjreferences
253
254local function initializer()
255 collectedobjrefs = job.fileobjreferences.collected or { }
256 tobesavedobjrefs = job.fileobjreferences.tobesaved or { }
257end
258
259job.register('job.fileobjreferences.collected', tobesavedobjrefs, initializer)
260
261local function flushembeddedfiles()
262 if enabled and next(filestreams) then
263 local e = pdfarray()
264 local f = pdfarray()
265 for tag, reference in sortedhash(filestreams) do
266 if not reference then
267 report_attachment("unreferenced file, tag %a",tag)
268 elseif referenced[tag] == "hidden" or referenced[tag] == "forced" then
269 e[#e+1] = pdfstring(tag)
270 e[#e+1] = reference
271 f[#f+1] = reference
272 else
273
274 f[#f+1] = reference
275 end
276 end
277 if #e > 0 then
278 lpdf.addtonames("EmbeddedFiles",pdfreference(pdfflushobject(pdfdictionary{ Names = e })))
279 end
280 if #f > 0 then
281 lpdf.addtocatalog("AF", pdfreference(pdfflushobject(f)))
282 end
283 end
284end
285
286lpdf.registerdocumentfinalizer(flushembeddedfiles,"embeddedfiles")
287
288function codeinjections.embedfile(specification)
289 if enabled then
290 local data = specification.data
291 local filename = specification.file
292 local name = specification.name or ""
293 local title = specification.title or ""
294 local hash = specification.hash or filename
295 local keepdir = specification.keepdir
296 local usedname = specification.usedname
297 local filetype = specification.filetype
298 local compress = specification.compress
299 local mimetype = specification.mimetype or specification.mime
300 if filename == "" then
301 filename = nil
302 end
303 if compress == nil then
304 compress = true
305 end
306 if data then
307 local r = filestreams[hash]
308 if r == false then
309 return nil
310 elseif r then
311 return r
312 elseif not filename then
313 filename = specification.tag
314 if not filename or filename == "" then
315 filename = specification.registered
316 end
317 if not filename or filename == "" then
318 filename = hash
319 end
320 end
321 else
322 if not filename then
323 return nil
324 end
325 local r = filestreams[hash]
326 if r == false then
327 return nil
328 elseif r then
329 return r
330 else
331 local foundname = resolvers.findbinfile(filename) or ""
332 if foundname == "" or not isfile(foundname) then
333 filestreams[filename] = false
334 return nil
335 else
336 specification.foundname = foundname
337 end
338 end
339 end
340
341 usedname = usedname ~= "" and usedname or filename or name
342 local basename = keepdir == true and usedname or basefilename(usedname)
343 local basename = gsub(basename,"%./","")
344 local savename = name ~= "" and name or basename
345 local foundname = specification.foundname or filename
346 if not filetype or filetype == "" then
347 filetype = name and (filename and filesuffix(filename)) or "txt"
348 end
349 savename = addfilesuffix(savename,filetype)
350 local a = pdfdictionary {
351 Type = pdfconstant("EmbeddedFile"),
352 Subtype = mimetype and mimetype ~= "" and pdfconstant(mimetype) or nil,
353 }
354 local f
355 if data then
356 f = pdfflushstreamobject(data,a)
357 specification.data = true
358 else
359 local attributes = lfs.attributes(foundname)
360 local modification = modificationtime(foundname)
361 a.Params = {
362 Size = attributes.size,
363 ModDate = lpdf.pdftimestamp(modification),
364 }
365 f = pdfflushstreamfileobject(foundname,a,compress)
366 end
367 local d = pdfdictionary {
368 Type = pdfconstant("Filespec"),
369 F = pdfstring(savename),
370
371 UF = pdfunicode(savename),
372 EF = pdfdictionary { F = pdfreference(f) },
373 Desc = title ~= "" and pdfunicode(title) or nil,
374 AFRelationship = pdfconstant("Unspecified"),
375 }
376 local r = pdfreference(pdfflushobject(d))
377 filestreams[hash] = r
378 if specification.forcereference == true then
379 referenced[hash] = "forced"
380 end
381 return r
382 end
383end
384
385function nodeinjections.attachfile(specification)
386 if enabled then
387 local registered = specification.registered or "<unset>"
388 local data = specification.data
389 local hash
390 local filename
391 if data then
392 hash = md5.HEX(data)
393 else
394 filename = specification.file
395 if not filename or filename == "" then
396 report_attachment("no file specified, using registered %a instead",registered)
397 filename = registered
398 specification.file = registered
399 end
400 local foundname = resolvers.findbinfile(filename) or ""
401 if foundname == "" or not isfile(foundname) then
402 report_attachment("invalid filename %a, ignoring registered %a",filename,registered)
403 return nil
404 else
405 specification.foundname = foundname
406 end
407 hash = filename
408 end
409 specification.hash = hash
410 nofattachments = nofattachments + 1
411 local registered = specification.registered or ""
412 local title = specification.title or ""
413 local subtitle = specification.subtitle or ""
414 local author = specification.author or ""
415 local onlyname = filename and filenameonly(filename) or ""
416 if registered == "" then
417 registered = filename
418 end
419 if author == "" and title ~= "" then
420 author = title
421 title = onlyname or ""
422 end
423 if author == "" then
424 author = onlyname or "<unknown>"
425 end
426 if title == "" then
427 title = registered
428 end
429 if title == "" and filename then
430 title = onlyname
431 end
432 local aref = attachments[registered]
433 if not aref then
434 aref = codeinjections.embedfile(specification)
435 attachments[registered] = aref
436 end
437 local reference = specification.reference
438 if reference and aref then
439 tobesavedobjrefs[reference] = aref[1]
440 end
441 if not aref then
442 report_attachment("skipping attachment, registered %a",registered)
443
444 elseif specification.method == v_hidden then
445 referenced[hash] = "hidden"
446 else
447 referenced[hash] = "annotation"
448 local name, appearance = analyzesymbol(specification.symbol,attachment_symbols)
449 local flags = specification.flags or 0
450 local d = pdfdictionary {
451 Subtype = pdfconstant("FileAttachment"),
452 FS = aref,
453 Contents = pdfunicode(title),
454 Name = name,
455 NM = pdfstring("attachment:"..nofattachments),
456 T = author ~= "" and pdfunicode(author) or nil,
457 Subj = subtitle ~= "" and pdfunicode(subtitle) or nil,
458 C = analyzecolor(specification.colorvalue,specification.colormodel),
459 CA = analyzetransparency(specification.transparencyvalue),
460 AP = appearance,
461 OC = analyzelayer(specification.layer),
462
463 F = (flags | 4) & (1023-1-2-32-256),
464 }
465 local width = specification.width or 0
466 local height = specification.height or 0
467 local depth = specification.depth or 0
468 local box = hpacknode(nodeinjections.annotation(width,height,depth,d()))
469 box.width = width
470 box.height = height
471 box.depth = depth
472 return box
473 end
474 end
475end
476
477function codeinjections.attachmentid(filename)
478 return filestreams[filename]
479end
480
481
482
483local nofcomments = 0
484local usepopupcomments = false
485
486local defaultattributes = {
487 ["xmlns"] = "http://www.w3.org/1999/xhtml",
488 ["xmlns:xfa"] = "http://www.xfa.org/schema/xfa-data/1.0/",
489 ["xfa:contentType"] = "text/html",
490 ["xfa:APIVersion"] = "Acrobat:8.0.0",
491 ["xfa:spec"] = "2.4",
492}
493
494local function checkcontent(text,option)
495 if option and option.xml then
496 local root = xml.convert(text)
497 if root and not root.er then
498 xml.checkbom(root)
499 local body = xml.first(root,"/body")
500 if body then
501 local at = body.at
502 for k, v in next, defaultattributes do
503 if not at[k] then
504 at[k] = v
505 end
506 end
507
508 local richcontent = xml.tostring(root)
509 return nil, pdfunicode(richcontent)
510 end
511 end
512 end
513 return pdfunicode(text)
514end
515
516function nodeinjections.comment(specification)
517 nofcomments = nofcomments + 1
518 local text = specification.data or ""
519 if specification.space ~= v_yes then
520 text = stripstring(text)
521 text = gsub(text,"[\n\r] *","\n")
522 end
523 text = gsub(text,"\r","\n")
524 local name, appearance = analyzesymbol(specification.symbol,comment_symbols)
525 local tag = specification.tag or ""
526 local title = specification.title or ""
527 local subtitle = specification.subtitle or ""
528 local author = specification.author or ""
529 local option = settings_to_hash(specification.option or "")
530 if author ~= "" then
531 if subtitle == "" then
532 subtitle = title
533 elseif title ~= "" then
534 subtitle = subtitle .. ", " .. title
535 end
536 title = author
537 end
538 if title == "" then
539 title = tag
540 end
541 local content, richcontent = checkcontent(text,option)
542 local d = pdfdictionary {
543 Subtype = pdfconstant("Text"),
544 Open = option[v_max] and pdfboolean(true) or nil,
545 Contents = content,
546 RC = richcontent,
547 T = title ~= "" and pdfunicode(title) or nil,
548 Subj = subtitle ~= "" and pdfunicode(subtitle) or nil,
549 C = analyzecolor(specification.colorvalue,specification.colormodel),
550 CA = analyzetransparency(specification.transparencyvalue),
551 OC = analyzelayer(specification.layer),
552 Name = name,
553 NM = pdfstring("comment:"..nofcomments),
554 AP = appearance,
555 }
556 local width = specification.width or 0
557 local height = specification.height or 0
558 local depth = specification.depth or 0
559 local box
560 if usepopupcomments then
561
562 local nd = pdfreserveobject()
563 local nc = pdfreserveobject()
564 local c = pdfdictionary {
565 Subtype = pdfconstant("Popup"),
566 Parent = pdfreference(nd),
567 }
568 d.Popup = pdfreference(nc)
569 box = hpacknode(
570 nodeinjections.annotation(0,0,0,d(),nd),
571 nodeinjections.annotation(width,height,depth,c(),nc)
572 )
573 else
574 box = hpacknode(nodeinjections.annotation(width,height,depth,d()))
575 end
576 box.width = width
577 box.height = height
578 box.depth = depth
579 return box
580end
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
613
614
615
616
617
618local ms, mu, mf = { }, { }, { }
619
620local function delayed(label)
621 local reserved = mu[label]
622 if not reserved then
623 reserved = pdfreserveobject()
624 mu[label] = reserved
625 end
626 return pdfreference(reserved)
627end
628
629local function checkedreference(ref)
630 local set, bug = structures.references.identify("",ref)
631 if not bug and #set > 0 then
632 return pdfaction(set)
633 end
634end
635
636local function insertrenderingwindow(specification)
637 local actions = nil
638 local label = specification.label
639 local openpage = specification.openpage
640 local closepage = specification.closepage
641 local option = settings_to_hash(specification.option)
642 if option[v_auto] then
643 if not openpage or openpage == "" then
644 openpage = variables.StartCurrentRendering
645 end
646 if not closepage or closepage == "" then
647 closepage = variables.StopCurrentRendering
648 end
649 end
650 openpage = checkedreference(openpage)
651 closepage = checkedreference(closepage)
652 if openpage or closepage then
653 actions = pdfdictionary {
654 PO = openpage,
655 PC = closepage,
656 }
657 end
658 local page = tonumber(specification.page) or texgetcount("realpageno")
659 local r = mu[label] or pdfreserveobject()
660 local a = pdfdictionary {
661 S = pdfconstant("Rendition"),
662 R = mf[label],
663 OP = 0,
664 AN = pdfreference(r),
665 }
666 local bs, bc = pdfborder()
667 local d = pdfdictionary {
668 Subtype = pdfconstant("Screen"),
669 P = pdfreference(pdfpagereference(page)),
670 A = a,
671 T = pdfunicode(label),
672 Border = bs,
673 C = bc,
674 AA = actions,
675 }
676 local width = specification.width or 0
677 local height = specification.height or 0
678 context(nodeinjections.annotation(width,height,0,d(),r))
679 return pdfreference(r)
680end
681
682
683
684local function insertrendering(specification)
685 local label = specification.label
686 local option = settings_to_hash(specification.option)
687 if not mf[label] then
688 local filename = specification.filename
689 if filename and filename ~= "" then
690 local isurl = find(filename,"://",1,true)
691 local mimetype = specification.mimetype or specification.mime
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716 local parameters = pdfdictionary {
717 Type = pdfconstant("MediaPermissions"),
718 TF = pdfstring("TEMPALWAYS"),
719 }
720 local descriptor = pdfdictionary {
721 Type = pdfconstant("Filespec"),
722 F = filename,
723 }
724 if isurl then
725 descriptor.FS = pdfconstant("URL")
726 descriptor = pdfreference(pdfflushobject(descriptor))
727 elseif option[v_embed] then
728 descriptor = codeinjections.embedfile {
729 file = filename,
730 mimetype = mimetype,
731 title = option[v_title],
732 compress = option[v_compress] or false,
733 forcereference = option[v_list] ~= v_no,
734 }
735 end
736 local clip = pdfdictionary {
737 Type = pdfconstant("MediaClip"),
738 S = pdfconstant("MCD"),
739 N = label,
740 CT = mimetype,
741 Alt = pdfarray { "", "file not found" },
742 D = descriptor,
743 P = pdfreference(pdfflushobject(parameters)),
744 }
745 local rendition = pdfdictionary {
746 Type = pdfconstant("Rendition"),
747 S = pdfconstant("MR"),
748 N = pdfunicode(label),
749 C = pdfreference(pdfflushobject(clip)),
750 }
751 mf[label] = pdfreference(pdfflushobject(rendition))
752 end
753 end
754end
755
756function codeinjections.processrendering(label)
757 local specification = interactions.renderings.rendering(label)
758 if specification then
759 insertrendering(specification)
760 else
761
762 end
763end
764
765
766
767local function flushrenderings()
768 if next(mf) then
769 local r = pdfarray()
770 for label, reference in sortedhash(mf) do
771 r[#r+1] = pdfunicode(label)
772 r[#r+1] = reference
773 end
774 lpdf.addtonames("Renditions",pdfreference(pdfflushobject(pdfdictionary{ Names = r })))
775 end
776end
777
778lpdf.registerdocumentfinalizer(flushrenderings,"renderings")
779
780function codeinjections.insertrenderingwindow(specification)
781 local label = specification.label
782 codeinjections.processrendering(label)
783 ms[label] = insertrenderingwindow(specification)
784end
785
786local function set(operation,label)
787 if not label or label == "" then
788
789 label = getmacro("currentrendering")
790 end
791 if label and label ~= "" then
792 codeinjections.processrendering(label)
793 return pdfdictionary {
794 S = pdfconstant("Rendition"),
795 OP = operation,
796 R = mf[label],
797 AN = ms[label] or delayed(label),
798 }
799 end
800end
801
802function executers.startrendering (label) return set(0,label) end
803function executers.stoprendering (label) return set(1,label) end
804function executers.pauserendering (label) return set(2,label) end
805function executers.resumerendering(label) return set(3,label) end
806 |