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