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