1if not modules then modules = { } end modules ['lpdf-ano'] = {
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
16local next, tostring, tonumber, rawget, type = next, tostring, tonumber, rawget, type
17local rep, format, find = string.rep, string.format, string.find
18local min = math.min
19local lpegmatch = lpeg.match
20local formatters = string.formatters
21local sortedkeys, concat = table.sortedkeys, table.concat
22
23local backends, lpdf = backends, lpdf
24
25local trace_references = false trackers.register("references.references", function(v) trace_references = v end)
26local trace_destinations = false trackers.register("references.destinations", function(v) trace_destinations = v end)
27local trace_bookmarks = false trackers.register("references.bookmarks", function(v) trace_bookmarks = v end)
28
29local log_destinations = false directives.register("destinations.log", function(v) log_destinations = v end)
30local untex_urls = true directives.register("references.untexurls", function(v) untex_urls = v end)
31
32local report_references = logs.reporter("backend","references")
33local report_destinations = logs.reporter("backend","destinations")
34local report_bookmarks = logs.reporter("backend","bookmarks")
35
36local variables = interfaces.variables
37local v_auto = variables.auto
38local v_page = variables.page
39local v_name = variables.name
40
41local factor = number.dimenfactors.bp
42
43local settings_to_array = utilities.parsers.settings_to_array
44
45local allocate = utilities.storage.allocate
46local setmetatableindex = table.setmetatableindex
47
48local nodeinjections = backends.pdf.nodeinjections
49local codeinjections = backends.pdf.codeinjections
50local registrations = backends.pdf.registrations
51
52local javascriptcode = interactions.javascripts.code
53
54local references = structures.references
55local bookmarks = structures.bookmarks
56
57local flaginternals = references.flaginternals
58local usedinternals = references.usedinternals
59local usedviews = references.usedviews
60
61local runners = references.runners
62local specials = references.specials
63local handlers = references.handlers
64local executers = references.executers
65
66local nodepool = nodes.pool
67
68local new_latelua = nodepool.latelua
69
70local texgetcount = tex.getcount
71
72local jobpositions = job.positions
73local getpos = jobpositions.getpos
74local gethpos = jobpositions.gethpos
75local getvpos = jobpositions.getvpos
76
77local pdfdictionary = lpdf.dictionary
78local pdfarray = lpdf.array
79local pdfreference = lpdf.reference
80local pdfunicode = lpdf.unicode
81local pdfconstant = lpdf.constant
82local pdfflushobject = lpdf.flushobject
83local pdfshareobjectreference = lpdf.shareobjectreference
84local pdfreserveobject = lpdf.reserveobject
85local pdfpagereference = lpdf.pagereference
86local pdfdelayedobject = lpdf.delayedobject
87local pdfregisterannotation = lpdf.registerannotation
88local pdfnull = lpdf.null
89local pdfaddtocatalog = lpdf.addtocatalog
90local pdfaddtonames = lpdf.addtonames
91local pdfaddtopageattributes = lpdf.addtopageattributes
92local pdfrectangle = lpdf.rectangle
93
94
95
96
97local pdf_uri = pdfconstant("URI")
98local pdf_gotor = pdfconstant("GoToR")
99local pdf_goto = pdfconstant("GoTo")
100local pdf_launch = pdfconstant("Launch")
101local pdf_javascript = pdfconstant("JavaScript")
102local pdf_link = pdfconstant("Link")
103local pdf_n = pdfconstant("N")
104local pdf_t = pdfconstant("T")
105local pdf_fit = pdfconstant("Fit")
106local pdf_named = pdfconstant("Named")
107
108local autoprefix = "#"
109local usedautoprefixes = { }
110
111local function registerautoprefix(name)
112 local internal = autoprefix .. name
113 if usedautoprefixes[internal] == nil then
114 usedautoprefixes[internal] = false
115 end
116 return internal
117end
118
119local function useautoprefix(name)
120 local internal = autoprefix .. name
121 usedautoprefixes[internal] = true
122 return internal
123end
124
125local function checkautoprefixes(destinations)
126 for k, v in next, usedautoprefixes do
127 if not v then
128 if trace_destinations then
129 report_destinations("flushing unused autoprefix %a",k)
130 end
131 destinations[k] = nil
132 end
133 end
134end
135
136local maxslice = 32
137
138local function pdfmakenametree(list,apply)
139 if not next(list) then
140 return
141 end
142 local slices = { }
143 local sorted = sortedkeys(list)
144 local size = #sorted
145 local maxslice = maxslice
146 if size <= 1.5*maxslice then
147 maxslice = size
148 end
149 for i=1,size,maxslice do
150 local amount = min(i+maxslice-1,size)
151 local names = pdfarray { }
152 local n = 0
153 for j=i,amount do
154 local name = sorted[j]
155 local target = list[name]
156 n = n + 1 ; names[n] = tostring(name)
157 n = n + 1 ; names[n] = apply and apply(target) or target
158 end
159 local first = sorted[i]
160 local last = sorted[amount]
161 local limits = pdfarray {
162 first,
163 last,
164 }
165 local d = pdfdictionary {
166 Names = names,
167 Limits = limits,
168 }
169 slices[#slices+1] = {
170 reference = pdfreference(pdfflushobject(d)),
171 limits = limits,
172 }
173 end
174 local function collectkids(slices,first,last)
175 local f = slices[first]
176 local l = slices[last]
177 if f and l then
178 local k = pdfarray()
179 local n = 0
180 local d = pdfdictionary {
181 Kids = k,
182 Limits = pdfarray {
183 f.limits[1],
184 l.limits[2],
185 },
186 }
187 for i=first,last do
188 n = n + 1 ; k[n] = slices[i].reference
189 end
190 return d
191 end
192 end
193 if #slices == 1 then
194 return slices[1].reference
195 else
196 while true do
197 local size = #slices
198 if size > maxslice then
199 local temp = { }
200 local n = 0
201 for i=1,size,maxslice do
202 local kids = collectkids(slices,i,min(i+maxslice-1,size))
203 if kids then
204 n = n + 1
205 temp[n] = {
206 reference = pdfreference(pdfflushobject(kids)),
207 limits = kids.Limits,
208 }
209 else
210
211 end
212 end
213 slices = temp
214 else
215 local kids = collectkids(slices,1,size)
216 if kids then
217 return pdfreference(pdfflushobject(kids))
218 else
219
220 return
221 end
222 end
223 end
224 end
225end
226
227lpdf.makenametree = pdfmakenametree
228
229
230
231
232
233
234
235local pdf_border_style = pdfarray { 0, 0, 0 }
236local pdf_border_color = nil
237local set_border = false
238
239local function pdfborder()
240 set_border = true
241 return pdf_border_style, pdf_border_color
242end
243
244lpdf.border = pdfborder
245
246directives.register("references.border",function(v)
247 if v and not set_border then
248 if type(v) == "string" then
249 local m = attributes.list[attributes.private('color')] or { }
250 local c = m and m[v]
251 local v = c and attributes.colors.value(c)
252 if v then
253 local r = v[3]
254 local g = v[4]
255 local b = v[5]
256
257
258
259 pdf_border_color = pdfarray { r, g, b }
260
261 end
262 end
263 if not pdf_border_color then
264 pdf_border_color = pdfarray { .6, .6, .6 }
265 end
266 pdf_border_style = pdfarray { 0, 0, .5 }
267 end
268end)
269
270
271
272
273
274
275
276
277local pagedestinations = setmetatableindex(function(t,k)
278 k = tonumber(k)
279 if not k or k <= 0 then
280 return pdfnull()
281 end
282 local v = rawget(t,k)
283 if v then
284
285 return v
286 end
287 local v = k > 0 and pdfarray {
288 pdfreference(pdfpagereference(k)),
289 pdf_fit,
290 } or pdfnull()
291 t[k] = v
292 return v
293end)
294
295local pagereferences = setmetatableindex(function(t,k)
296 k = tonumber(k)
297 if not k or k <= 0 then
298 return nil
299 end
300 local v = rawget(t,k)
301 if v then
302 return v
303 end
304 local v = pdfdictionary {
305 S = pdf_goto,
306 D = pagedestinations[k],
307 }
308 t[k] = v
309 return v
310end)
311
312local defaultdestination = pdfarray { 0, pdf_fit }
313
314
315
316local destinations = { }
317local reported = setmetatableindex("table")
318
319local function pdfregisterdestination(name,reference)
320 local d = destinations[name]
321 if d then
322 if not reported[name][reference] then
323 report_destinations("ignoring duplicate destination %a with reference %a",name,reference)
324 reported[name][reference] = true
325 end
326 else
327 destinations[name] = reference
328 end
329end
330
331lpdf.registerdestination = pdfregisterdestination
332
333logs.registerfinalactions(function()
334 if log_destinations and next(destinations) then
335 local report = logs.startfilelogging("references","used destinations")
336 local n = 0
337 for destination, pagenumber in table.sortedhash(destinations) do
338 report("% 4i : %-5s : %s",pagenumber,usedviews[destination] or defaultview,destination)
339 n = n + 1
340 end
341 logs.stopfilelogging()
342 report_destinations("%s destinations saved in log file",n)
343 end
344end)
345
346local function pdfdestinationspecification()
347 if next(destinations) then
348 checkautoprefixes(destinations)
349 local r = pdfmakenametree(destinations,pdfreference)
350 if r then
351 pdfaddtonames("Dests",r)
352 end
353 if not log_destinations then
354 destinations = nil
355 end
356 end
357end
358
359lpdf.destinationspecification = pdfdestinationspecification
360
361lpdf.registerdocumentfinalizer(pdfdestinationspecification,"collect destinations")
362
363
364
365local destinations = { }
366
367local f_xyz = formatters["<< /D [ %i 0 R /XYZ %.6N %.6N null ] >>"]
368local f_fit = formatters["<< /D [ %i 0 R /Fit ] >>"]
369local f_fitb = formatters["<< /D [ %i 0 R /FitB ] >>"]
370local f_fith = formatters["<< /D [ %i 0 R /FitH %.6N ] >>"]
371local f_fitv = formatters["<< /D [ %i 0 R /FitV %.6N ] >>"]
372local f_fitbh = formatters["<< /D [ %i 0 R /FitBH %.6N ] >>"]
373local f_fitbv = formatters["<< /D [ %i 0 R /FitBV %.6N ] >>"]
374local f_fitr = formatters["<< /D [ %i 0 R /FitR %.6N %.6N %.6N %.6N ] >>"]
375
376local v_standard = variables.standard
377local v_frame = variables.frame
378local v_width = variables.width
379local v_minwidth = variables.minwidth
380local v_height = variables.height
381local v_minheight = variables.minheight
382local v_fit = variables.fit
383local v_tight = variables.tight
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409local destinationactions = {
410 [v_standard] = function(r,w,h,d,o)
411 local tx, ty = getpos()
412 return f_xyz(r,tx*factor,(ty+h+2*o)*factor)
413 end,
414 [v_frame] = function(r,w,h,d,o)
415 return f_fitr(r,pdfrectangle(w,h,d,o))
416 end,
417 [v_width] = function(r,w,h,d,o)
418 return f_fith(r,(getvpos()+h+o)*factor)
419 end,
420 [v_minwidth] = function(r,w,h,d,o)
421 return f_fitbh(r,(getvpos()+h+o)*factor)
422 end,
423 [v_height] = function(r,w,h,d,o)
424 return f_fitv(r,(gethpos())*factor)
425 end,
426 [v_minheight] = function(r,w,h,d,o)
427 return f_fitbv(r,(gethpos())*factor)
428 end,
429 [v_tight] = f_fitb,
430 [v_fit] = f_fit,
431}
432
433local mapping = {
434 [v_standard] = v_standard, xyz = v_standard,
435 [v_frame] = v_frame, fitr = v_frame,
436 [v_width] = v_width, fith = v_width,
437 [v_minwidth] = v_minwidth, fitbh = v_minwidth,
438 [v_height] = v_height, fitv = v_height,
439 [v_minheight] = v_minheight, fitbv = v_minheight,
440 [v_fit] = v_fit, fit = v_fit,
441 [v_tight] = v_tight, fitb = v_tight,
442}
443
444local defaultview = v_fit
445local defaultaction = destinationactions[defaultview]
446local offset = 0
447
448directives.register("destinations.offset", function(v)
449 offset = string.todimen(v) or 0
450end)
451
452
453
454
455
456local pagedestinations = setmetatableindex(function(t,k)
457 local v = pdfdelayedobject(f_fit(k))
458 t[k] = v
459 return v
460end)
461
462local function flushdestination(specification)
463 local names = specification.names
464 local view = specification.view
465 local r = pdfpagereference(texgetcount("realpageno"))
466 if (references.innermethod ~= v_name) and (view == defaultview or not view or view == "") then
467 r = pagedestinations[r]
468 else
469 local action = view and destinationactions[view] or defaultaction
470 r = pdfdelayedobject(action(r,specification.width,specification.height,specification.depth,offset))
471 end
472 for n=1,#names do
473 local name = names[n]
474 if name then
475 pdfregisterdestination(name,r)
476 end
477 end
478end
479
480function nodeinjections.destination(width,height,depth,names,view)
481
482 view = view and mapping[view] or defaultview
483 if trace_destinations then
484 report_destinations("width %p, height %p, depth %p, names %|t, view %a",width,height,depth,names,view)
485 end
486 local method = references.innermethod
487 local noview = view == defaultview
488 local doview = false
489
490
491
492 if method == v_page then
493 for n=1,#names do
494 local name = names[n]
495 local used = usedviews[name]
496 if used and used ~= true then
497
498 elseif type(name) == "number" then
499
500
501
502
503 usedviews[name] = view
504 names[n] = false
505
506 else
507 usedviews[name] = view
508 end
509 end
510 elseif method == v_name then
511 for n=1,#names do
512 local name = names[n]
513 local used = usedviews[name]
514 if used and used ~= true then
515
516 elseif type(name) == "number" then
517 local used = usedinternals[name]
518 usedviews[name] = view
519 names[n] = registerautoprefix(name)
520 doview = true
521 else
522 usedviews[name] = view
523 doview = true
524 end
525 end
526 else
527 for n=1,#names do
528 local name = names[n]
529 if usedviews[name] then
530
531 elseif type(name) == "number" then
532 if noview then
533 usedviews[name] = view
534 names[n] = false
535 else
536 local used = usedinternals[name]
537 if used and used ~= defaultview then
538 usedviews[name] = view
539 names[n] = registerautoprefix(name)
540 doview = true
541 else
542 names[n] = false
543 end
544 end
545 else
546 usedviews[name] = view
547 doview = true
548 end
549 end
550 end
551 if doview then
552 return new_latelua {
553 action = flushdestination,
554 width = width,
555 height = height,
556 depth = depth,
557 names = names,
558 view = view,
559 }
560 end
561end
562
563
564
565local function pdflinkpage(page)
566 return pagereferences[page]
567end
568
569local function pdflinkinternal(internal,page)
570
571 if internal then
572 flaginternals[internal] = true
573 local used = usedinternals[internal]
574 if used == defaultview or used == true then
575 return pagereferences[page]
576 else
577 if type(internal) ~= "string" then
578 internal = useautoprefix(internal)
579 end
580 return pdfdictionary {
581 S = pdf_goto,
582 D = internal,
583 }
584 end
585 else
586 return pagereferences[page]
587 end
588end
589
590local function pdflinkname(destination,internal,page)
591 local method = references.innermethod
592 if method == v_auto then
593 local used = defaultview
594 if internal then
595 flaginternals[internal] = true
596 used = usedinternals[internal] or defaultview
597 end
598 if used == defaultview then
599 return pagereferences[page]
600 else
601 return pdfdictionary {
602 S = pdf_goto,
603 D = destination,
604 }
605 end
606 elseif method == v_name then
607
608 return pdfdictionary {
609 S = pdf_goto,
610 D = destination,
611 }
612 else
613 return pagereferences[page]
614 end
615end
616
617
618
619local function pdffilelink(filename,destination,page,actions)
620 if not filename or filename == "" or file.basename(filename) == tex.jobname then
621 return false
622 end
623 filename = file.addsuffix(filename,"pdf")
624 if (not destination or destination == "") or (references.outermethod == v_page) then
625 destination = pdfarray { (page or 1) - 1, pdf_fit }
626 end
627 return pdfdictionary {
628 S = pdf_gotor,
629 F = filename,
630 D = destination or defaultdestination,
631 NewWindow = actions.newwindow and true or nil,
632 }
633end
634
635local untex = references.urls.untex
636
637local function pdfurllink(url,destination,page)
638 if not url or url == "" then
639 return false
640 end
641 if untex_urls then
642 url = untex(url)
643 end
644 if destination and destination ~= "" then
645 url = url .. "#" .. destination
646 end
647 return pdfdictionary {
648 S = pdf_uri,
649 URI = url,
650 }
651end
652
653local function pdflaunch(program,parameters)
654 if not program or program == "" then
655 return false
656 end
657 return pdfdictionary {
658 S = pdf_launch,
659 F = program,
660 D = ".",
661 P = parameters ~= "" and parameters or nil
662 }
663end
664
665local function pdfjavascript(name,arguments)
666 local script = javascriptcode(name,arguments)
667 if script then
668 return pdfdictionary {
669 S = pdf_javascript,
670 JS = script,
671 }
672 end
673end
674
675local function pdfaction(actions)
676 local nofactions = #actions
677 if nofactions > 0 then
678 local a = actions[1]
679 local action = runners[a.kind]
680 if action then
681 action = action(a,actions)
682 end
683 if action then
684 local first = action
685 for i=2,nofactions do
686 local a = actions[i]
687 local what = runners[a.kind]
688 if what then
689 what = what(a,actions)
690 end
691 if action == what then
692
693 elseif what then
694 action.Next = what
695 action = what
696 else
697
698 return nil
699 end
700 end
701 return first, actions.n or #actions
702 end
703 end
704end
705
706lpdf.action = pdfaction
707
708function codeinjections.prerollreference(actions)
709 if actions then
710 local main, n = pdfaction(actions)
711 if main then
712 local bs, bc = pdfborder()
713 main = pdfdictionary {
714 Subtype = pdf_link,
715
716 Border = pdfshareobjectreference(bs),
717 C = bc,
718 H = (not actions.highlight and pdf_n) or nil,
719 A = pdfshareobjectreference(main),
720 F = 4,
721 }
722 return main("A"), n
723 end
724 end
725end
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747local hashed = { }
748local nofunique = 0
749local nofused = 0
750local nofspecial = 0
751local share = true
752
753local f_annot = formatters["<< /Type /Annot %s /Rect [ %.6N %.6N %.6N %.6N ] >>"]
754local f_quadp = formatters["<< /Type /Annot %s /QuadPoints [ %s ] /Rect [ %.6N %.6N %.6N %.6N ] >>"]
755
756directives.register("references.sharelinks", function(v)
757 share = v
758end)
759
760setmetatableindex(hashed,function(t,k)
761 local v = pdfdelayedobject(k)
762 if share then
763 t[k] = v
764 end
765 nofunique = nofunique + 1
766 return v
767end)
768
769local function toquadpoints(paths)
770 local t, n = { }, 0
771 for i=1,#paths do
772 local path = paths[i]
773 local size = #path
774 for j=1,size do
775 local p = path[j]
776 n = n + 1 ; t[n] = p[1]
777 n = n + 1 ; t[n] = p[2]
778 end
779 local m = size % 4
780 if m > 0 then
781 local p = path[size]
782 for j=size+1,m do
783 n = n + 1 ; t[n] = p[1]
784 n = n + 1 ; t[n] = p[2]
785 end
786 end
787 end
788 return concat(t," ")
789end
790
791local function finishreference(specification)
792 local prerolled = specification.prerolled
793 local quadpoints = specification.mesh
794 local llx, lly,
795 urx, ury = pdfrectangle(specification.width,specification.height,specification.depth)
796 local specifier = nil
797 if quadpoints and #quadpoints > 0 then
798 specifier = f_quadp(prerolled,toquadpoints(quadpoints),llx,lly,urx,ury)
799 else
800 specifier = f_annot(prerolled,llx,lly,urx,ury)
801 end
802 nofused = nofused + 1
803 return pdfregisterannotation(hashed[specifier])
804end
805
806local function finishannotation(specification)
807 local prerolled = specification.prerolled
808 local objref = specification.objref
809 if type(prerolled) == "function" then
810 prerolled = prerolled()
811 end
812 local annot = f_annot(prerolled,pdfrectangle(specification.width,specification.height,specification.depth))
813 if objref then
814 pdfdelayedobject(annot,objref)
815 else
816 objref = pdfdelayedobject(annot)
817 end
818 nofspecial = nofspecial + 1
819 return pdfregisterannotation(objref)
820end
821
822function nodeinjections.reference(width,height,depth,prerolled,mesh)
823 if prerolled then
824 if trace_references then
825 report_references("link: width %p, height %p, depth %p, prerolled %a",width,height,depth,prerolled)
826 end
827 return new_latelua {
828 action = finishreference,
829 width = width,
830 height = height,
831 depth = depth,
832 prerolled = prerolled,
833 mesh = mesh,
834 }
835 end
836end
837
838function nodeinjections.annotation(width,height,depth,prerolled,objref)
839 if prerolled then
840 if trace_references then
841 report_references("special: width %p, height %p, depth %p, prerolled %a",width,height,depth,
842 type(prerolled) == "string" and prerolled or "-")
843 end
844 return new_latelua {
845 action = finishannotation,
846 width = width,
847 height = height,
848 depth = depth,
849 prerolled = prerolled,
850 objref = objref or false,
851 }
852 end
853end
854
855
856
857
858
859local annotations = nil
860
861function lpdf.registerannotation(n)
862 if annotations then
863 annotations[#annotations+1] = pdfreference(n)
864 else
865 annotations = pdfarray { pdfreference(n) }
866 end
867end
868
869pdfregisterannotation = lpdf.registerannotation
870
871function lpdf.annotationspecification()
872 if annotations then
873 local r = pdfdelayedobject(tostring(annotations))
874 if r then
875 pdfaddtopageattributes("Annots",pdfreference(r))
876 end
877 annotations = nil
878 end
879end
880
881lpdf.registerpagefinalizer(lpdf.annotationspecification,"finalize annotations")
882
883statistics.register("pdf annotations", function()
884 if nofused > 0 or nofspecial > 0 then
885 return format("%s links (%s unique), %s special",nofused,nofunique,nofspecial)
886 else
887 return nil
888 end
889end)
890
891
892
893local splitter = lpeg.splitat(",",true)
894
895runners["inner"] = function(var,actions)
896 local internal = false
897 local name = nil
898 local method = references.innermethod
899 local vi = var.i
900 local page = var.r
901 if vi then
902 local vir = vi.references
903 if vir then
904
905
906 local reference = vir.reference
907 if reference and reference ~= "" then
908 reference = lpegmatch(splitter,reference) or reference
909 var.inner = reference
910 local prefix = var.p
911 if prefix and prefix ~= "" then
912 var.prefix = prefix
913 name = prefix .. ":" .. reference
914 else
915 name = reference
916 end
917 end
918 internal = vir.internal
919 if internal then
920 flaginternals[internal] = true
921 end
922 end
923 end
924 if name then
925 return pdflinkname(name,internal,page)
926 elseif internal then
927 return pdflinkinternal(internal,page)
928 elseif page then
929 return pdflinkpage(page)
930 else
931
932 end
933end
934
935runners["inner with arguments"] = function(var,actions)
936 report_references("todo: inner with arguments")
937 return false
938end
939
940runners["outer"] = function(var,actions)
941 local file, url = references.checkedfileorurl(var.outer,var.outer)
942 if file then
943 return pdffilelink(file,var.arguments,nil,actions)
944 elseif url then
945 return pdfurllink(url,var.arguments,nil,actions)
946 end
947end
948
949runners["outer with inner"] = function(var,actions)
950 return pdffilelink(references.checkedfile(var.outer),var.inner,var.r,actions)
951end
952
953runners["special outer with operation"] = function(var,actions)
954 local handler = specials[var.special]
955 return handler and handler(var,actions)
956end
957
958runners["special outer"] = function(var,actions)
959 report_references("todo: special outer")
960 return false
961end
962
963runners["special"] = function(var,actions)
964 local handler = specials[var.special]
965 return handler and handler(var,actions)
966end
967
968runners["outer with inner with arguments"] = function(var,actions)
969 report_references("todo: outer with inner with arguments")
970 return false
971end
972
973runners["outer with special and operation and arguments"] = function(var,actions)
974 report_references("todo: outer with special and operation and arguments")
975 return false
976end
977
978runners["outer with special"] = function(var,actions)
979 report_references("todo: outer with special")
980 return false
981end
982
983runners["outer with special and operation"] = function(var,actions)
984 report_references("todo: outer with special and operation")
985 return false
986end
987
988runners["special operation"] = runners["special"]
989runners["special operation with arguments"] = runners["special"]
990
991local reported = { }
992
993function specials.internal(var,actions)
994 local o = var.operation
995 local i = o and tonumber(o)
996 local v = i and references.internals[i]
997 if v then
998 flaginternals[i] = true
999 return pdflinkinternal(i,v.references.realpage)
1000 end
1001 local v = i or o or "<unset>"
1002 if not reported[v] then
1003 report_references("no internal reference %a",v)
1004 reported[v] = true
1005 end
1006end
1007
1008
1009
1010specials.i = specials.internal
1011
1012local pages = references.pages
1013
1014function specials.page(var,actions)
1015 local file = var.f
1016 if file then
1017 return pdffilelink(references.checkedfile(file),nil,var.operation,actions)
1018 else
1019 local p = var.r
1020 if not p then
1021 p = pages[var.operation]
1022 if type(p) == "function" then
1023 p = p()
1024 else
1025 p = references.realpageofpage(tonumber(p))
1026 end
1027 end
1028 return pdflinkpage(p or var.operation)
1029 end
1030end
1031
1032function specials.realpage(var,actions)
1033 local file = var.f
1034 if file then
1035 return pdffilelink(references.checkedfile(file),nil,var.operation,actions)
1036 else
1037 return pdflinkpage(var.operation)
1038 end
1039end
1040
1041function specials.userpage(var,actions)
1042 local file = var.f
1043 if file then
1044 return pdffilelink(references.checkedfile(file),nil,var.operation,actions)
1045 else
1046 local p = var.r
1047 if not p then
1048 p = var.operation
1049 if p then
1050 p = references.realpageofpage(tonumber(p))
1051 end
1052
1053
1054
1055 end
1056 return pdflinkpage(p or var.operation)
1057 end
1058end
1059
1060function specials.deltapage(var,actions)
1061 local p = tonumber(var.operation)
1062 if p then
1063 p = references.checkedrealpage(p + texgetcount("realpageno"))
1064 return pdflinkpage(p)
1065 end
1066end
1067
1068
1069
1070function specials.section(var,actions)
1071
1072 local sectionname = var.arguments
1073 local destination = var.operation
1074 local internal = structures.sections.internalreference(sectionname,destination)
1075 if internal then
1076 var.special = "internal"
1077 var.operation = internal
1078 var.arguments = nil
1079 return specials.internal(var,actions)
1080 end
1081end
1082
1083
1084
1085local splitter = lpeg.splitat(":")
1086
1087function specials.order(var,actions)
1088 local operation = var.operation
1089 if operation then
1090 local kind, name, n = lpegmatch(splitter,operation)
1091 local order = structures.lists.ordered[kind]
1092 order = order and order[name]
1093 local v = order[tonumber(n)]
1094 local r = v and v.references.realpage
1095 if r then
1096 var.operation = r
1097 return specials.page(var,actions)
1098 end
1099 end
1100end
1101
1102function specials.url(var,actions)
1103 return pdfurllink(references.checkedurl(var.operation),var.arguments,nil,actions)
1104end
1105
1106function specials.file(var,actions)
1107 return pdffilelink(references.checkedfile(var.operation),var.arguments,nil,actions)
1108end
1109
1110function specials.fileorurl(var,actions)
1111 local file, url = references.checkedfileorurl(var.operation,var.operation)
1112 if file then
1113 return pdffilelink(file,var.arguments,nil,actions)
1114 elseif url then
1115 return pdfurllink(url,var.arguments,nil,actions)
1116 end
1117end
1118
1119function specials.program(var,content)
1120 local program = references.checkedprogram(var.operation)
1121 return pdflaunch(program,var.arguments)
1122end
1123
1124function specials.javascript(var)
1125 return pdfjavascript(var.operation,var.arguments)
1126end
1127
1128specials.JS = specials.javascript
1129
1130executers.importform = pdfdictionary { S = pdf_named, N = pdfconstant("AcroForm:ImportFDF") }
1131executers.exportform = pdfdictionary { S = pdf_named, N = pdfconstant("AcroForm:ExportFDF") }
1132executers.first = pdfdictionary { S = pdf_named, N = pdfconstant("FirstPage") }
1133executers.previous = pdfdictionary { S = pdf_named, N = pdfconstant("PrevPage") }
1134executers.next = pdfdictionary { S = pdf_named, N = pdfconstant("NextPage") }
1135executers.last = pdfdictionary { S = pdf_named, N = pdfconstant("LastPage") }
1136executers.backward = pdfdictionary { S = pdf_named, N = pdfconstant("GoBack") }
1137executers.forward = pdfdictionary { S = pdf_named, N = pdfconstant("GoForward") }
1138executers.print = pdfdictionary { S = pdf_named, N = pdfconstant("Print") }
1139executers.exit = pdfdictionary { S = pdf_named, N = pdfconstant("Quit") }
1140executers.close = pdfdictionary { S = pdf_named, N = pdfconstant("Close") }
1141executers.save = pdfdictionary { S = pdf_named, N = pdfconstant("Save") }
1142executers.savenamed = pdfdictionary { S = pdf_named, N = pdfconstant("SaveAs") }
1143executers.opennamed = pdfdictionary { S = pdf_named, N = pdfconstant("Open") }
1144executers.help = pdfdictionary { S = pdf_named, N = pdfconstant("HelpUserGuide") }
1145executers.toggle = pdfdictionary { S = pdf_named, N = pdfconstant("FullScreen") }
1146executers.search = pdfdictionary { S = pdf_named, N = pdfconstant("Find") }
1147executers.searchagain = pdfdictionary { S = pdf_named, N = pdfconstant("FindAgain") }
1148executers.gotopage = pdfdictionary { S = pdf_named, N = pdfconstant("GoToPage") }
1149executers.query = pdfdictionary { S = pdf_named, N = pdfconstant("AcroSrch:Query") }
1150executers.queryagain = pdfdictionary { S = pdf_named, N = pdfconstant("AcroSrch:NextHit") }
1151executers.fitwidth = pdfdictionary { S = pdf_named, N = pdfconstant("FitWidth") }
1152executers.fitheight = pdfdictionary { S = pdf_named, N = pdfconstant("FitHeight") }
1153
1154local function fieldset(arguments)
1155
1156 return nil
1157end
1158
1159function executers.resetform(arguments)
1160 arguments = (type(arguments) == "table" and arguments) or settings_to_array(arguments)
1161 return pdfdictionary {
1162 S = pdfconstant("ResetForm"),
1163 Field = fieldset(arguments[1])
1164 }
1165end
1166
1167local formmethod = "post"
1168local formformat = "xml"
1169
1170
1171
1172local flags = {
1173 get = {
1174 html = 12, fdf = 8, xml = 40,
1175 },
1176 post = {
1177 html = 4, fdf = 0, xml = 32,
1178 }
1179}
1180
1181function executers.submitform(arguments)
1182 arguments = (type(arguments) == "table" and arguments) or settings_to_array(arguments)
1183 local flag = flags[formmethod] or flags.post
1184 flag = (flag and (flag[formformat] or flag.xml)) or 32
1185 return pdfdictionary {
1186 S = pdfconstant("SubmitForm"),
1187 F = arguments[1],
1188 Field = fieldset(arguments[2]),
1189 Flags = flag,
1190
1191 }
1192end
1193
1194local pdf_hide = pdfconstant("Hide")
1195
1196function executers.hide(arguments)
1197 return pdfdictionary {
1198 S = pdf_hide,
1199 H = true,
1200 T = arguments,
1201 }
1202end
1203
1204function executers.show(arguments)
1205 return pdfdictionary {
1206 S = pdf_hide,
1207 H = false,
1208 T = arguments,
1209 }
1210end
1211
1212local pdf_movie = pdfconstant("Movie")
1213local pdf_start = pdfconstant("Start")
1214local pdf_stop = pdfconstant("Stop")
1215local pdf_resume = pdfconstant("Resume")
1216local pdf_pause = pdfconstant("Pause")
1217
1218local function movie_or_sound(operation,what,arguments)
1219 arguments = (type(arguments) == "table" and arguments) or settings_to_array(arguments)
1220 return pdfdictionary {
1221 S = pdf_movie,
1222 T = format("%s %s",what,arguments[1] or "noname"),
1223 Operation = operation,
1224 }
1225end
1226
1227function executers.startmovie (arguments) return movie_or_sound(pdf_start ,"movie",arguments) end
1228function executers.stopmovie (arguments) return movie_or_sound(pdf_stop ,"movie",arguments) end
1229function executers.resumemovie(arguments) return movie_or_sound(pdf_resume,"movie",arguments) end
1230function executers.pausemovie (arguments) return movie_or_sound(pdf_pause ,"movie",arguments) end
1231
1232function executers.startsound (arguments) return movie_or_sound(pdf_start ,"sound",arguments) end
1233function executers.stopsound (arguments) return movie_or_sound(pdf_stop ,"sound",arguments) end
1234function executers.resumesound(arguments) return movie_or_sound(pdf_resume,"sound",arguments) end
1235function executers.pausesound (arguments) return movie_or_sound(pdf_pause ,"sound",arguments) end
1236
1237function specials.action(var)
1238 local operation = var.operation
1239 if var.operation and operation ~= "" then
1240 local e = executers[operation]
1241 if type(e) == "table" then
1242 return e
1243 elseif type(e) == "function" then
1244 return e(var.arguments)
1245 end
1246 end
1247end
1248
1249local function build(levels,start,parent,method,nested)
1250 local startlevel = levels[start].level
1251 local noflevels = #levels
1252 local i = start
1253 local n = 0
1254 local child, entry, m, prev, first, last, f, l
1255 while i and i <= noflevels do
1256 local current = levels[i]
1257 if current.usedpage == false then
1258
1259 i = i + 1
1260 else
1261 local level = current.level
1262 local title = current.title
1263 local reference = current.reference
1264 local opened = current.opened
1265 local reftype = type(reference)
1266 local block = nil
1267 local variant = "unknown"
1268 if reftype == "table" then
1269
1270 variant = "list"
1271 block = reference.block
1272 realpage = reference.realpage
1273 elseif reftype == "string" then
1274 local resolved = references.identify("",reference)
1275 realpage = resolved and structures.references.setreferencerealpage(resolved) or 0
1276 if realpage > 0 then
1277 variant = "realpage"
1278 realpage = realpage
1279 reference = structures.pages.collected[realpage]
1280 block = reference and reference.block
1281 end
1282 elseif reftype == "number" then
1283 if reference > 0 then
1284 variant = "realpage"
1285 realpage = reference
1286 reference = structures.pages.collected[realpage]
1287 block = reference and reference.block
1288 end
1289 else
1290
1291 end
1292 current.block = block
1293 if variant == "unknown" then
1294
1295 i = i + 1
1296
1297 elseif (level < startlevel) or (i > 1 and block ~= levels[i-1].block) then
1298 if nested then
1299 if entry then
1300 pdfflushobject(child,entry)
1301 else
1302 report_bookmarks("error 1")
1303 end
1304 return i, n, first, last
1305 else
1306 report_bookmarks("confusing level change at level %a around %a",level,title)
1307 startlevel = level
1308 end
1309 end
1310 if level == startlevel then
1311 if trace_bookmarks then
1312 report_bookmarks("%3i %w%s %s",realpage,(level-1)*2,(opened and "+") or "-",title)
1313 end
1314 local prev = child
1315 child = pdfreserveobject()
1316 if entry then
1317 entry.Next = child and pdfreference(child)
1318 pdfflushobject(prev,entry)
1319 end
1320 local action = nil
1321 if variant == "list" then
1322 action = pdflinkinternal(reference.internal,reference.realpage)
1323 elseif variant == "realpage" then
1324 action = pagereferences[realpage]
1325 else
1326
1327 end
1328 entry = pdfdictionary {
1329 Title = pdfunicode(title),
1330 Parent = parent,
1331 Prev = prev and pdfreference(prev),
1332 A = action,
1333 }
1334
1335 if not first then
1336 first, last = child, child
1337 end
1338 prev = child
1339 last = prev
1340 n = n + 1
1341 i = i + 1
1342 elseif i < noflevels and level > startlevel then
1343 i, m, f, l = build(levels,i,pdfreference(child),method,true)
1344 if entry then
1345 entry.Count = (opened and m) or -m
1346 if m > 0 then
1347 entry.First = pdfreference(f)
1348 entry.Last = pdfreference(l)
1349 end
1350 else
1351 report_bookmarks("error 2")
1352 end
1353 else
1354
1355 i, m, f, l = build(levels,i,pdfreference(child),method,true)
1356 if entry then
1357 entry.Count = (opened and m) or -m
1358 if m > 0 then
1359 entry.First = pdfreference(f)
1360 entry.Last = pdfreference(l)
1361 end
1362 pdfflushobject(child,entry)
1363 else
1364 report_bookmarks("error 3")
1365 end
1366 return i, n, first, last
1367 end
1368 end
1369 end
1370 pdfflushobject(child,entry)
1371 return nil, n, first, last
1372end
1373
1374function codeinjections.addbookmarks(levels,method)
1375 if levels and #levels > 0 then
1376 local parent = pdfreserveobject()
1377 local _, m, first, last = build(levels,1,pdfreference(parent),method or "internal",false)
1378 local dict = pdfdictionary {
1379 Type = pdfconstant("Outlines"),
1380 First = pdfreference(first),
1381 Last = pdfreference(last),
1382 Count = m,
1383 }
1384 pdfflushobject(parent,dict)
1385 pdfaddtocatalog("Outlines",lpdf.reference(parent))
1386 end
1387end
1388
1389
1390
1391lpdf.registerdocumentfinalizer(function() bookmarks.place() end,1,"bookmarks")
1392 |