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 <const> = variables.hidden
59local v_auto <const> = variables.auto
60local v_embed <const> = variables.embed
61local v_max <const> = variables.max
62local v_yes <const> = variables.yes
63local v_no <const> = variables.no
64local v_compress <const> = variables.compress
65local v_list <const> = variables.list
66local v_title <const> = 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
288
289
290
291local hashed = sha2.HEX256
292local shared = table.setmetatableindex("table")
293
294function codeinjections.embedfile(specification)
295 if enabled or specification.force then
296 local data = specification.data
297 local filename = specification.file
298 local name = specification.name or ""
299 local title = specification.title or ""
300 local hash = specification.hash or filename
301 local keepdir = specification.keepdir
302 local usedname = specification.usedname
303 local filetype = specification.filetype
304 local compress = specification.compress
305 local index = specification.index
306 local mimetype = specification.mimetype or specification.mime
307 local relation = specification.relation or "Unspecified"
308 if filename == "" then
309 filename = nil
310 end
311 if compress == nil then
312 compress = true
313 end
314 if data then
315 local r = filestreams[hash]
316 if r == false then
317 return nil
318 elseif r then
319 return r
320 elseif not filename then
321 filename = specification.tag
322 if not filename or filename == "" then
323 filename = specification.registered
324 end
325 if not filename or filename == "" then
326 filename = hash
327 end
328 end
329 else
330 if not filename then
331 return nil
332 end
333 local r = filestreams[hash]
334 if r == false then
335 return nil
336 elseif r then
337 return r
338 else
339 local foundname = resolvers.findbinfile(filename) or ""
340 if foundname == "" or not isfile(foundname) then
341 filestreams[filename] = false
342 return nil
343 else
344 specification.foundname = foundname
345 end
346 end
347 end
348
349 usedname = usedname ~= "" and usedname or filename or name
350 local basename = keepdir == true and usedname or basefilename(usedname)
351 local basename = gsub(basename,"%./","")
352 local savename = name ~= "" and name or basename
353 local foundname = specification.foundname or filename
354 if not filetype or filetype == "" then
355 filetype = name and (filename and filesuffix(filename)) or "txt"
356 end
357 savename = addfilesuffix(savename,filetype)
358
359
360
361local fdict = nil
362
363
364
365
366
367
368
369
370
371 local a = pdfdictionary {
372 Type = pdfconstant("EmbeddedFile"),
373 Subtype = mimetype and mimetype ~= "" and pdfconstant(mimetype) or nil,
374 }
375 if data then
376 fdict = pdfflushstreamobject(data,a)
377 specification.data = true
378 else
379 local attributes = lfs.attributes(foundname)
380 local modification = modificationtime(foundname)
381 a.Params = {
382 Size = attributes.size,
383 ModDate = lpdf.pdftimestamp(modification),
384 }
385 fdict = pdfflushstreamfileobject(foundname,a,compress)
386 end
387
388 fdict = pdfdictionary { F = pdfreference(fdict) }
389
390
391
392
393 if title == "" and lpdf.majorversion() > 1 then
394 title = basefilename(savename)
395 end
396 local d = pdfdictionary {
397 Type = pdfconstant("Filespec"),
398 F = pdfstring(savename),
399
400 UF = pdfunicode(savename),
401
402 EF = fdict,
403 Desc = title ~= "" and pdfunicode(title) or nil,
404 AFRelationship = pdfconstant(relation),
405 LMTX_Index = index or nil,
406 }
407 local r = pdfreference(pdfflushobject(d))
408 filestreams[hash] = r
409 if specification.forcereference == true then
410 referenced[hash] = "forced"
411 end
412 return r
413 end
414end
415
416function nodeinjections.attachfile(specification)
417 if enabled then
418 local registered = specification.registered or "<unset>"
419 local data = specification.data
420 local hash
421 local filename
422 if data then
423 hash = md5.HEX(data)
424 else
425 filename = specification.file
426 if not filename or filename == "" then
427 report_attachment("no file specified, using registered %a instead",registered)
428 filename = registered
429 specification.file = registered
430 end
431 local foundname = resolvers.findbinfile(filename) or ""
432 if foundname == "" or not isfile(foundname) then
433 report_attachment("invalid filename %a, ignoring registered %a",filename,registered)
434 return nil
435 else
436 specification.foundname = foundname
437 end
438 hash = filename
439 end
440 specification.hash = hash
441 nofattachments = nofattachments + 1
442 local registered = specification.registered or ""
443 local title = specification.title or ""
444 local subtitle = specification.subtitle or ""
445 local author = specification.author or ""
446 local onlyname = filename and filenameonly(filename) or ""
447 if registered == "" then
448 registered = filename
449 end
450 if author == "" and title ~= "" then
451 author = title
452 title = onlyname or ""
453 end
454 if author == "" then
455 author = onlyname or "<unknown>"
456 end
457 if title == "" then
458 title = registered
459 end
460 if title == "" and filename then
461 title = onlyname
462 end
463 local aref = attachments[registered]
464 if not aref then
465 aref = codeinjections.embedfile(specification)
466 attachments[registered] = aref
467 end
468 local reference = specification.reference
469 if reference and aref then
470 tobesavedobjrefs[reference] = aref[1]
471 end
472 if not aref then
473 report_attachment("skipping attachment, registered %a",registered)
474
475 elseif specification.method == v_hidden then
476 referenced[hash] = "hidden"
477 else
478 referenced[hash] = "annotation"
479 local name, appearance = analyzesymbol(specification.symbol,attachment_symbols)
480 local flags = specification.flags or 0
481 local d = pdfdictionary {
482 Subtype = pdfconstant("FileAttachment"),
483 FS = aref,
484 Contents = pdfunicode(title),
485 Name = name,
486 NM = pdfstring("attachment:"..nofattachments),
487 T = author ~= "" and pdfunicode(author) or nil,
488 Subj = subtitle ~= "" and pdfunicode(subtitle) or nil,
489 C = analyzecolor(specification.colorvalue,specification.colormodel),
490 CA = analyzetransparency(specification.transparencyvalue),
491 AP = appearance,
492 OC = analyzelayer(specification.layer),
493
494 F = (flags | 4) & (1023-1-2-32-256),
495 }
496 local width = specification.width or 0
497 local height = specification.height or 0
498 local depth = specification.depth or 0
499 local box = hpacknode(nodeinjections.annotation(width,height,depth,d()))
500 box.width = width
501 box.height = height
502 box.depth = depth
503 return box
504 end
505 end
506end
507
508function codeinjections.attachmentid(filename)
509 return filestreams[filename]
510end
511
512
513
514local nofcomments = 0
515local usepopupcomments = false
516
517local defaultattributes = {
518 ["xmlns"] = "http://www.w3.org/1999/xhtml",
519 ["xmlns:xfa"] = "http://www.xfa.org/schema/xfa-data/1.0/",
520 ["xfa:contentType"] = "text/html",
521 ["xfa:APIVersion"] = "Acrobat:8.0.0",
522 ["xfa:spec"] = "2.4",
523}
524
525local function checkcontent(text,option)
526 if option and option.xml then
527 local root = xml.convert(text)
528 if root and not root.er then
529 xml.checkbom(root)
530 local body = xml.first(root,"/body")
531 if body then
532 local at = body.at
533 for k, v in next, defaultattributes do
534 if not at[k] then
535 at[k] = v
536 end
537 end
538
539 local richcontent = xml.tostring(root)
540 return nil, pdfunicode(richcontent)
541 end
542 end
543 end
544 return pdfunicode(text)
545end
546
547function nodeinjections.comment(specification)
548 nofcomments = nofcomments + 1
549 local text = specification.data or ""
550 if specification.space ~= v_yes then
551 text = stripstring(text)
552 text = gsub(text,"[\n\r] *","\n")
553 end
554 text = gsub(text,"\r","\n")
555 local name, appearance = analyzesymbol(specification.symbol,comment_symbols)
556 local tag = specification.tag or ""
557 local title = specification.title or ""
558 local subtitle = specification.subtitle or ""
559 local author = specification.author or ""
560 local option = settings_to_hash(specification.option or "")
561 if author ~= "" then
562 if subtitle == "" then
563 subtitle = title
564 elseif title ~= "" then
565 subtitle = subtitle .. ", " .. title
566 end
567 title = author
568 end
569 if title == "" then
570 title = tag
571 end
572 local content, richcontent = checkcontent(text,option)
573 local d = pdfdictionary {
574 Subtype = pdfconstant("Text"),
575 Open = option[v_max] and pdfboolean(true) or nil,
576 Contents = content,
577 RC = richcontent,
578 T = title ~= "" and pdfunicode(title) or nil,
579 Subj = subtitle ~= "" and pdfunicode(subtitle) or nil,
580 C = analyzecolor(specification.colorvalue,specification.colormodel),
581 CA = analyzetransparency(specification.transparencyvalue),
582 OC = analyzelayer(specification.layer),
583 Name = name,
584 NM = pdfstring("comment:"..nofcomments),
585 AP = appearance,
586 }
587 local width = specification.width or 0
588 local height = specification.height or 0
589 local depth = specification.depth or 0
590 local box
591 if usepopupcomments then
592
593 local nd = pdfreserveobject()
594 local nc = pdfreserveobject()
595 local c = pdfdictionary {
596 Subtype = pdfconstant("Popup"),
597 Parent = pdfreference(nd),
598 }
599 d.Popup = pdfreference(nc)
600 box = hpacknode(
601 nodeinjections.annotation(0,0,0,d(),nd),
602 nodeinjections.annotation(width,height,depth,c(),nc)
603 )
604 else
605 box = hpacknode(nodeinjections.annotation(width,height,depth,d()))
606 end
607 box.width = width
608 box.height = height
609 box.depth = depth
610 return box
611end
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649local ms, mu, mf = { }, { }, { }
650
651local function delayed(label)
652 local reserved = mu[label]
653 if not reserved then
654 reserved = pdfreserveobject()
655 mu[label] = reserved
656 end
657 return pdfreference(reserved)
658end
659
660local function checkedreference(ref)
661 local set, bug = structures.references.identify("",ref)
662 if not bug and #set > 0 then
663 return pdfaction(set)
664 end
665end
666
667local function insertrenderingwindow(specification)
668 local actions = nil
669 local label = specification.label
670 local openpage = specification.openpage
671 local closepage = specification.closepage
672 local option = settings_to_hash(specification.option)
673 if option[v_auto] then
674 if not openpage or openpage == "" then
675 openpage = variables.StartCurrentRendering
676 end
677 if not closepage or closepage == "" then
678 closepage = variables.StopCurrentRendering
679 end
680 end
681 openpage = checkedreference(openpage)
682 closepage = checkedreference(closepage)
683 if openpage or closepage then
684 actions = pdfdictionary {
685 PO = openpage,
686 PC = closepage,
687 }
688 end
689 local page = tonumber(specification.page) or texgetcount("realpageno")
690 local r = mu[label] or pdfreserveobject()
691 local a = pdfdictionary {
692 S = pdfconstant("Rendition"),
693 R = mf[label],
694 OP = 0,
695 AN = pdfreference(r),
696 }
697 local bs, bc = pdfborder()
698 local d = pdfdictionary {
699 Subtype = pdfconstant("Screen"),
700 P = pdfreference(pdfpagereference(page)),
701 A = a,
702 T = pdfunicode(label),
703 Border = bs,
704 C = bc,
705 AA = actions,
706 }
707 local width = specification.width or 0
708 local height = specification.height or 0
709 context(nodeinjections.annotation(width,height,0,d(),r))
710 return pdfreference(r)
711end
712
713
714
715local function insertrendering(specification)
716 local label = specification.label
717 local option = settings_to_hash(specification.option)
718 if not mf[label] then
719 local filename = specification.filename
720 if filename and filename ~= "" then
721 local isurl = find(filename,"://",1,true)
722 local mimetype = specification.mimetype or specification.mime
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747 local parameters = pdfdictionary {
748 Type = pdfconstant("MediaPermissions"),
749 TF = pdfstring("TEMPALWAYS"),
750 }
751 local descriptor = pdfdictionary {
752 Type = pdfconstant("Filespec"),
753 F = filename,
754 }
755 if isurl then
756 descriptor.FS = pdfconstant("URL")
757 descriptor = pdfreference(pdfflushobject(descriptor))
758 elseif option[v_embed] then
759 descriptor = codeinjections.embedfile {
760 file = filename,
761 mimetype = mimetype,
762 title = option[v_title],
763 compress = option[v_compress] or false,
764 forcereference = option[v_list] ~= v_no,
765 }
766 end
767 local clip = pdfdictionary {
768 Type = pdfconstant("MediaClip"),
769 S = pdfconstant("MCD"),
770 N = label,
771 CT = mimetype,
772 Alt = pdfarray { "", "file not found" },
773 D = descriptor,
774 P = pdfreference(pdfflushobject(parameters)),
775 }
776 local rendition = pdfdictionary {
777 Type = pdfconstant("Rendition"),
778 S = pdfconstant("MR"),
779 N = pdfunicode(label),
780 C = pdfreference(pdfflushobject(clip)),
781 }
782 mf[label] = pdfreference(pdfflushobject(rendition))
783 end
784 end
785end
786
787function codeinjections.processrendering(label)
788 local specification = interactions.renderings.rendering(label)
789 if specification then
790 insertrendering(specification)
791 else
792
793 end
794end
795
796
797
798local function flushrenderings()
799 if next(mf) then
800 local r = pdfarray()
801 for label, reference in sortedhash(mf) do
802 r[#r+1] = pdfunicode(label)
803 r[#r+1] = reference
804 end
805 lpdf.addtonames("Renditions",pdfreference(pdfflushobject(pdfdictionary{ Names = r })))
806 end
807end
808
809lpdf.registerdocumentfinalizer(flushrenderings,"renderings")
810
811function codeinjections.insertrenderingwindow(specification)
812 local label = specification.label
813 codeinjections.processrendering(label)
814 ms[label] = insertrenderingwindow(specification)
815end
816
817local function set(operation,label)
818 if not label or label == "" then
819
820 label = getmacro("currentrendering")
821 end
822 if label and label ~= "" then
823 codeinjections.processrendering(label)
824 return pdfdictionary {
825 S = pdfconstant("Rendition"),
826 OP = operation,
827 R = mf[label],
828 AN = ms[label] or delayed(label),
829 }
830 end
831end
832
833function executers.startrendering (label) return set(0,label) end
834function executers.stoprendering (label) return set(1,label) end
835function executers.pauserendering (label) return set(2,label) end
836function executers.resumerendering(label) return set(3,label) end
837 |