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, max = math.min, math.max
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 <const> = variables.auto
45local v_page <const> = variables.page
46local v_name <const> = variables.name
47
48local factor <const> = 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 pdfstring = lpdf.string
91local pdfconstant = lpdf.constant
92local pdfnull = lpdf.null
93local pdfaddtocatalog = lpdf.addtocatalog
94local pdfaddtonames = lpdf.addtonames
95local pdfaddtopageattributes = lpdf.addtopageattributes
96local pdfrectangle = lpdf.rectangle
97local pdfquads = lpdf.quads
98
99local pdfflushobject = lpdf.flushobject
100local pdfshareobjectreference = lpdf.shareobjectreference
101local pdfreserveobject = lpdf.reserveobject
102local pdfpagereference = lpdf.pagereference
103local pdfdelayedobject = lpdf.delayedobject
104local pdfmajorversion = lpdf.majorversion
105
106
107
108local pdfregisterannotation
109
110
111
112
113local pdf_uri = pdfconstant("URI")
114local pdf_gotor = pdfconstant("GoToR")
115local pdf_goto = pdfconstant("GoTo")
116local pdf_launch = pdfconstant("Launch")
117local pdf_javascript = pdfconstant("JavaScript")
118local pdf_highlight = pdfconstant("Highlight")
119local pdf_link = pdfconstant("Link")
120local pdf_n = pdfconstant("N")
121local pdf_t = pdfconstant("T")
122local pdf_fit = pdfconstant("Fit")
123local pdf_named = pdfconstant("Named")
124
125local autoprefix = "#"
126local usedautoprefixes = { }
127
128local crappytaggingmode = 0
129
130updaters.register("structures.tagging",function(version)
131 if tex.conditionals.c_strc_tags_global then
132 crappytaggingmode = tonumber(version) or 0
133 end
134end)
135
136function codeinjections.setautoprefix(prefix)
137 autoprefix = prefix ~= "" and prefix or autoprefix
138end
139
140local function registerautoprefix(name)
141 local internal = autoprefix .. name
142 if usedautoprefixes[internal] == nil then
143 usedautoprefixes[internal] = false
144 end
145 return internal
146end
147
148local function useautoprefix(name)
149 local internal = autoprefix .. name
150 usedautoprefixes[internal] = true
151 return internal
152end
153
154local function checkautoprefixes(destinations)
155 for k, v in next, usedautoprefixes do
156 if not v then
157 if trace_destinations then
158 report_destinations("flushing unused autoprefix %a",k)
159 end
160 destinations[k] = nil
161 end
162 end
163end
164
165
166
167
168local pdfmakenametree do
169
170 local maxslice = 32
171 local convert = pdfstring
172
173 directives.register("backend.pdf.unicodenames",function(v)
174 convert = v and pdfunicode or pdfstring
175 end)
176
177 pdfmakenametree = function(list,apply)
178 if not next(list) then
179 return
180 end
181 local slices = { }
182 local sorted = sortedkeys(list)
183 local size = #sorted
184 local maxslice = maxslice
185 if size <= 1.5*maxslice then
186 maxslice = size
187 end
188 for i=1,size,maxslice do
189 local amount = min(i+maxslice-1,size)
190 local names = pdfarray { }
191 local n = 0
192 for j=i,amount do
193 local name = sorted[j]
194 local target = list[name]
195 n = n + 1 ; names[n] = convert(name)
196 n = n + 1 ; names[n] = apply and apply(target) or target
197 end
198 local first = sorted[i]
199 local last = sorted[amount]
200 local limits = pdfarray {
201 convert(first),
202 convert(last),
203 }
204 local d = pdfdictionary {
205 Names = names,
206 Limits = limits,
207 }
208 slices[#slices+1] = {
209 reference = pdfreference(pdfflushobject(d)),
210 limits = limits,
211 }
212 end
213 local function collectkids(slices,first,last)
214 local f = slices[first]
215 local l = slices[last]
216 if f and l then
217 local k = pdfarray()
218 local n = 0
219 local d = pdfdictionary {
220 Kids = k,
221 Limits = pdfarray {
222 f.limits[1],
223 l.limits[2],
224 },
225 }
226 for i=first,last do
227 n = n + 1 ; k[n] = slices[i].reference
228 end
229 return d
230 end
231 end
232 if #slices == 1 then
233 return slices[1].reference
234 else
235 while true do
236 local size = #slices
237 if size > maxslice then
238 local temp = { }
239 local n = 0
240 for i=1,size,maxslice do
241 local kids = collectkids(slices,i,min(i+maxslice-1,size))
242 if kids then
243 n = n + 1
244 temp[n] = {
245 reference = pdfreference(pdfflushobject(kids)),
246 limits = kids.Limits,
247 }
248 else
249
250 end
251 end
252 slices = temp
253 else
254 local kids = collectkids(slices,1,size)
255 if kids then
256 return pdfreference(pdfflushobject(kids))
257 else
258
259 return
260 end
261 end
262 end
263 end
264 end
265
266 lpdf.makenametree = pdfmakenametree
267
268end
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291local pdf_border_style = pdfarray { 0, 0, 0 }
292local pdf_border_color = nil
293local set_border = false
294local use_borders = false
295
296local function pdfborder(actions)
297 set_border = true
298 if use_borders and actions then
299 local action = actions[1]
300 if action then
301 local kind = action.kind
302 local special = action.special or ""
303 local border = use_borders[kind]
304 if not border then
305 report_references("no border color defined for %a",kind)
306 use_borders[kind] = { [""] = pdf_border_color }
307 else
308 border = border[special]
309 if border then
310 return pdf_border_style, border
311 else
312 report_references("no border color defined for %a : %a",kind,special)
313 use_borders[kind] = { [special] = pdf_border_color }
314 end
315 end
316 end
317 end
318 return pdf_border_style, pdf_border_color
319end
320
321lpdf.border = pdfborder
322
323
324
325local pdfcolorarray do
326
327 local colorlist = attributes.list[attributes.private('color')]
328 local colorvalue = attributes.colors.value
329
330 pdfcolorarray = function(v)
331 if v then
332 v = colorlist and colorlist[v]
333 if v then
334 v = colorvalue(v)
335 if v then
336 v = pdfarray { v[3], v[4], v[5] }
337 end
338 end
339 end
340 if not v then
341 v = pdfarray { .8, .8, .8 }
342 end
343 return v
344 end
345
346end
347
348directives.register("references.border",function(v)
349 if v and not set_border then
350 if type(v) == "string" then
351 if find(v,":") then
352 if not use_borders then
353 use_borders = { }
354 end
355 local kind, special, color = match(v,"^([a-z%s]+)+*([a-z%s]*):(.+)$")
356 if kind then
357 local c = pdfcolorarray(color)
358 if c then
359 local k = use_borders[kind]
360 if k then
361 k[special] = c
362 else
363 use_borders[kind] = { [special] = c }
364 end
365 end
366 end
367 else
368 pdf_border_color = pdfcolorarray(v)
369 end
370 end
371 if not pdf_border_color then
372 pdf_border_color = pdfarray { .6, .6, .6 }
373 end
374 pdf_border_style = pdfarray { 0, 0, .5 }
375 end
376end)
377
378
379
380
381
382
383
384
385local pagedestinations = setmetatableindex(function(t,k)
386 k = tonumber(k)
387 if not k or k <= 0 then
388 return pdfnull()
389 end
390 local v = rawget(t,k)
391 if not v then
392 v = k > 0 and pdfarray {
393 pdfreference(pdfpagereference(k)),
394 pdf_fit,
395 } or pdfnull()
396 t[k] = v
397 end
398 return v
399end)
400
401local crappypagedestinations = setmetatableindex(function(t,k)
402 k = tonumber(k)
403 if not k or k <= 0 then
404 return pdfnull()
405 end
406 local v = rawget(t,k)
407 if not v then
408 local o = codeinjections.getreferencestructureobject(false,k) or 0
409 v = o > 0 and pdfarray {
410 pdfreference(o),
411 pdf_fit,
412 } or pdfnull()
413 t[k] = v
414 end
415 return v
416end)
417
418local pagereferences = setmetatableindex(function(t,k)
419 k = tonumber(k)
420 if not k or k <= 0 then
421 return nil
422 end
423 local v = rawget(t,k)
424 if v then
425 return v
426 end
427 local v = pdfdictionary {
428 S = pdf_goto,
429 D = pagedestinations[k],
430 SD = crappytaggingmode > 1 and crappypagedestinations[k] or nil,
431 }
432 t[k] = v
433 return v
434end)
435
436local defaultdestination = pdfarray { 0, pdf_fit }
437
438
439
440local destinations = { }
441local reported = setmetatableindex("table")
442
443local function pdfregisterdestination(name,reference)
444 local d = destinations[name]
445 if d then
446 if not reported[name][reference] then
447 report_destinations("ignoring duplicate destination %a with reference %a",name,reference)
448 reported[name][reference] = true
449 end
450 else
451 destinations[name] = reference
452 end
453end
454
455lpdf.registerdestination = pdfregisterdestination
456
457logs.registerfinalactions(function()
458 if log_destinations and next(destinations) then
459 local report = logs.startfilelogging("references","used destinations")
460 local n = 0
461 for destination, pagenumber in table.sortedhash(destinations) do
462 report("% 4i : %-5s : %s",pagenumber,usedviews[destination] or defaultview,destination)
463 n = n + 1
464 end
465 logs.stopfilelogging()
466 report_destinations("%s destinations saved in log file",n)
467 end
468end)
469
470local function pdfdestinationspecification()
471 if next(destinations) then
472 checkautoprefixes(destinations)
473 local r = pdfmakenametree(destinations,pdfreference)
474 if r then
475 pdfaddtonames("Dests",r)
476 end
477 if not log_destinations then
478 destinations = nil
479 end
480 end
481end
482
483lpdf.destinationspecification = pdfdestinationspecification
484
485lpdf.registerdocumentfinalizer(pdfdestinationspecification,"collect destinations")
486
487
488
489local destinations = { }
490
491local v_standard <const> = variables.standard
492local v_frame <const> = variables.frame
493local v_width <const> = variables.width
494local v_minwidth <const> = variables.minwidth
495local v_height <const> = variables.height
496local v_minheight <const> = variables.minheight
497local v_fit <const> = variables.fit
498local v_tight <const> = variables.tight
499
500local mapping = {
501 [v_standard] = v_standard, xyz = v_standard,
502 [v_frame] = v_frame, fitr = v_frame,
503 [v_width] = v_width, fith = v_width,
504 [v_minwidth] = v_minwidth, fitbh = v_minwidth,
505 [v_height] = v_height, fitv = v_height,
506 [v_minheight] = v_minheight, fitbv = v_minheight,
507 [v_fit] = v_fit, fit = v_fit,
508 [v_tight] = v_tight, fitb = v_tight,
509}
510
511local defaultview = v_fit
512local offset = 0
513
514local c_realpageno <const> = tex.iscount("realpageno")
515
516
517
518
519
520
521
522
523
524
525
526
527local destinationactions, defaultaction do
528
529 local f_xyz = formatters["<< /D [ %i 0 R /XYZ %.6N %.6N null ] >>"]
530 local f_fit = formatters["<< /D [ %i 0 R /Fit ] >>"]
531 local f_fitb = formatters["<< /D [ %i 0 R /FitB ] >>"]
532 local f_fith = formatters["<< /D [ %i 0 R /FitH %.6N ] >>"]
533 local f_fitv = formatters["<< /D [ %i 0 R /FitV %.6N ] >>"]
534 local f_fitbh = formatters["<< /D [ %i 0 R /FitBH %.6N ] >>"]
535 local f_fitbv = formatters["<< /D [ %i 0 R /FitBV %.6N ] >>"]
536 local f_fitr = formatters["<< /D [ %i 0 R /FitR %.6N %.6N %.6N %.6N ] >>"]
537
538 destinationactions = {
539
540 [v_standard] = function(r,w,h,d,o)
541 local tx, ty = getpos()
542 return f_xyz(r,tx*factor,(ty+h+2*o)*factor)
543 end,
544
545 [v_frame] = function(r,w,h,d,o)
546 return f_fitr(r,pdfrectangle(w,h,d,o))
547 end,
548
549 [v_width] = function(r,w,h,d,o)
550 return f_fith(r,(getvpos()+h+o)*factor)
551 end,
552
553 [v_minwidth] = function(r,w,h,d,o)
554 return f_fitbh(r,(getvpos()+h+o)*factor)
555 end,
556
557 [v_height] = function(r,w,h,d,o)
558 return f_fitv(r,(gethpos())*factor)
559 end,
560
561 [v_minheight] = function(r,w,h,d,o)
562 return f_fitbv(r,(gethpos())*factor)
563 end,
564
565 [v_tight] = f_fitb,
566
567 [v_fit] = f_fit,
568 }
569
570 defaultaction = destinationactions[defaultview]
571
572end
573
574local xdestinationactions, xdefaultaction do
575
576 local f_xyz = formatters["<< /D [ %i 0 R /XYZ %.6N %.6N null ] /SD [ %i 0 R /XYZ %.6N %.6N null ] >>"]
577 local f_fit = formatters["<< /D [ %i 0 R /Fit ] /SD [ %i 0 R /Fit ] >>"]
578 local f_fitb = formatters["<< /D [ %i 0 R /FitB ] /SD [ %i 0 R /FitB ] >>"]
579 local f_fith = formatters["<< /D [ %i 0 R /FitH %.6N ] /SD [ %i 0 R /FitH %.6N ] >>"]
580 local f_fitv = formatters["<< /D [ %i 0 R /FitV %.6N ] /SD[ %i 0 R /FitV %.6N ] >>"]
581 local f_fitbh = formatters["<< /D [ %i 0 R /FitBH %.6N ] /SD [ %i 0 R /FitBH %.6N ] >>"]
582 local f_fitbv = formatters["<< /D [ %i 0 R /FitBV %.6N ] /SD [ %i 0 R /FitBV %.6N ] >>"]
583 local f_fitr = formatters["<< /D [ %i 0 R /FitR %.6N %.6N %.6N %.6N ] /SD [ %i 0 R /FitR %.6N %.6N %.6N %.6N ] >>"]
584
585 xdestinationactions = {
586 [v_standard] = function(r,w,h,d,o,s)
587 local tx, ty = getpos()
588 local x = tx*factor
589 local y = (ty+h+2*o)*factor
590 return f_xyz(r,x,y,s,x,y)
591 end,
592 [v_frame] = function(r,w,h,d,o,s)
593 return f_fitr(r,pdfrectangle(w,h,d,o),s,pdfrectangle(w,h,d,o))
594 end,
595 [v_width] = function(r,w,h,d,o,s)
596 local v = (getvpos()+h+o)*factor
597 return f_fith(r,v,s,v)
598 end,
599 [v_minwidth] = function(r,w,h,d,o,s)
600 local h = (getvpos()+h+o)*factor
601 return f_fitbh(r,h,s,h)
602 end,
603 [v_height] = function(r,w,h,d,o,s)
604 local v = gethpos()*factor
605 return f_fitv(r,v,s,v)
606 end,
607 [v_minheight] = function(r,w,h,d,o,s)
608 local h = gethpos()*factor
609 return f_fitbv(r,h,s,h)
610 end,
611 [v_tight] = function(r,w,h,d,o,s)
612 return f_fitb(r,s)
613 end,
614 [v_fit] = function(r,w,h,d,o,s)
615 return f_fit(r,s)
616 end,
617 }
618
619 xdefaultaction = xdestinationactions[defaultview]
620
621end
622
623directives.register("destinations.offset", function(v)
624 offset = string.todimen(v) or 0
625end)
626
627
628
629
630
631local f_fit = formatters["<< /D [ %i 0 R /Fit ] >>"]
632
633local pagedestinations = setmetatableindex(function(t,k)
634 local v = pdfdelayedobject(f_fit(k))
635 t[k] = v
636 return v
637end)
638
639local function flushdestination(specification)
640 local names = specification.names
641 local view = specification.view
642 local page = texgetcount(c_realpageno)
643 local r = pdfpagereference(page)
644 if (crappytaggingmode < 2) and (references.innermethod ~= v_name) and (view == defaultview or not view or view == "") then
645 r = pagedestinations[r]
646 else
647 local action, o
648 if crappytaggingmode > 1 then
649 local internal = specification.internal
650 if internal or page then
651 o = codeinjections.getreferencestructureobject(internal,page)
652 if o and o > 0 then
653 action = view and xdestinationactions[view] or xdefaultaction
654local d = autoprefix .. internal
655local f = false
656for i=1,#names do
657 if names[i] == d then
658 f = true
659 break
660 end
661end
662if not f then
663 names[#names+1] = d
664end
665 else
666 o = nil
667 end
668 end
669 end
670 if not action then
671 action = view and destinationactions[view] or defaultaction
672 end
673
674
675
676
677
678
679 r = pdfdelayedobject(action(r,specification.width,specification.height,specification.depth,offset,o))
680
681 end
682 for n=1,#names do
683 local name = names[n]
684 if name then
685 pdfregisterdestination(name,r)
686 end
687 end
688end
689
690function nodeinjections.destination(width,height,depth,names,view,internal)
691
692 view = view and mapping[view] or defaultview
693 if trace_destinations then
694 report_destinations("width %p, height %p, depth %p, names %|t, view %a",width,height,depth,names,view)
695 end
696 local method = references.innermethod
697 local noview = view == defaultview
698 local doview = false
699
700
701
702 if method == v_page then
703 for n=1,#names do
704 local name = names[n]
705 if name then
706 local used = usedviews[name]
707 if used and used ~= true then
708
709 elseif type(name) == "number" then
710
711
712
713
714 usedviews[name] = view
715 names[n] = false
716
717 else
718 usedviews[name] = view
719 end
720 end
721 end
722 elseif method == v_name then
723 for n=1,#names do
724 local name = names[n]
725 if name then
726 local used = usedviews[name]
727 if used and used ~= true then
728
729 elseif type(name) == "number" then
730 local used = usedinternals[name]
731 usedviews[name] = view
732 names[n] = registerautoprefix(name)
733 doview = true
734 else
735 usedviews[name] = view
736 doview = true
737 end
738 end
739 end
740 else
741 for n=1,#names do
742 local name = names[n]
743 if name then
744 if usedviews[name] then
745
746 elseif type(name) == "number" then
747 if noview then
748 usedviews[name] = view
749 names[n] = false
750 else
751 local used = usedinternals[name]
752 if used and used ~= defaultview then
753 usedviews[name] = view
754 names[n] = registerautoprefix(name)
755 doview = true
756 else
757 names[n] = false
758 end
759 end
760 else
761 usedviews[name] = view
762 doview = true
763 end
764 end
765 end
766 end
767 if doview or crappytaggingmode > 0 then
768 return new_latelua {
769
770 action = flushdestination,
771 width = width,
772 height = height,
773 depth = depth,
774 names = names,
775 view = view,
776 internal = internal,
777 }
778 end
779end
780
781
782
783local function pdflinkpage(page)
784 return pagereferences[page]
785end
786
787local function pdflinkinternal(internal,page)
788
789 if internal then
790 flaginternals[internal] = true
791 if crappytaggingmode > 1 then
792 local o = codeinjections.getreferencestructureobject(internal,page)
793 if o and o > 0 then
794 if type(internal) ~= "string" then
795 internal = useautoprefix(internal)
796 end
797 return pdfdictionary {
798 S = pdf_goto,
799 D = internal,
800 SD = pdfarray { pdfreference(o), pdf_fit }
801 }
802 end
803 end
804 local used = usedinternals[internal]
805 if type(internal) ~= "string" then
806 internal = useautoprefix(internal)
807 end
808 if used == defaultview or used == true then
809 return pagereferences[page]
810 else
811 if type(internal) ~= "string" then
812 internal = useautoprefix(internal)
813 end
814 return pdfdictionary {
815 S = pdf_goto,
816 D = internal,
817 SD = crappytaggingmode > 1 and crappypagedestinations[page] or nil,
818 }
819 end
820 else
821 return pagereferences[page]
822 end
823end
824
825local function pdflinkname(destination,internal,page)
826 local method = references.innermethod
827 if method == v_auto then
828 local used = defaultview
829 if internal then
830 flaginternals[internal] = true
831 used = usedinternals[internal] or defaultview
832 end
833 if used == defaultview then
834 return pagereferences[page]
835 else
836 return pdfdictionary {
837 S = pdf_goto,
838 D = destination,
839 SD = crappytaggingmode > 1 and crappypagedestinations[page] or nil,
840 }
841 end
842 elseif method == v_name then
843
844 return pdfdictionary {
845 S = pdf_goto,
846 D = destination,
847 SD = crappytaggingmode > 1 and crappypagedestinations[page] or nil,
848 }
849 else
850 return pagereferences[page]
851 end
852end
853
854
855
856local pdffilelink do
857
858 local valid = table.setmetatableindex(function(t,filename)
859 local found = false
860 if lfs.isfile(filename) then
861 report_destinations("loading destinations from file %a",filename)
862 local pdffile = lpdf.epdf.load(filename)
863 if pdffile then
864 local pages = pdffile.pages
865 local nofpages = pdffile.nofpages
866 local destinations = pdffile.destinations
867 if pages and nofpages > 0 and destinations then
868 local reverse = swapped(pages)
869 local total = 0
870 found = { }
871 for k, v in next, destinations do
872 local D = v.D
873 if D then
874 found[k] = reverse[D[1]]
875 total = total + 1
876 end
877 end
878 t[filename] = found
879 report_destinations("%i destinations on %i pages found",total,nofpages)
880 end
881 end
882 end
883 return found
884 end)
885
886 local pagefromhash = structures.references.pagefromhash
887
888 pdffilelink = function(filename,destination,page,actions)
889 if not filename or filename == "" or file.basename(filename) == tex.jobname then
890 return false
891 end
892 filename = file.addsuffix(filename,"pdf")
893
894 local forcepage = false
895 if not destination or destination == "" then
896 forcepage = true
897 elseif references.outermethod == v_page then
898 if not page then
899 local hash = valid[filename]
900 page = hash and hash[destination]
901 if not page or trace_externals then
902 report_destinations("no %s destination %a in file %a","page",destination,filename)
903 end
904 end
905 forcepage = true
906 else
907 local hash = valid[filename]
908 if hash then
909 local p = nil
910 p, destination = pagefromhash(hash,destination,page,actions)
911 if p then
912 if references.outermethod == v_name then
913
914 elseif page then
915 if p ~= page then
916 report_destinations("page %i for destination %a in %a conflicts, %i expected",page,destination,filename,p)
917 page = p
918 end
919 forcepage = true
920 elseif p then
921 page = p
922 forcepage = true
923 end
924 else
925 if not page or trace_externals then
926 report_destinations("no %s destination %a in file %a","name",destination,filename)
927 end
928 forcepage = true
929 end
930
931
932 end
933 end
934 if forcepage then
935 destination = pdfarray { (page or 1) - 1, pdf_fit }
936 end
937 return pdfdictionary {
938 S = pdf_gotor,
939 F = filename,
940 D = destination or defaultdestination,
941 NewWindow = actions.newwindow and true or nil,
942 }
943 end
944
945end
946
947local untex = references.urls.untex
948
949local function pdfurllink(url,destination,page)
950 if not url or url == "" then
951 return false
952 end
953 if untex_urls then
954 url = untex(url)
955 end
956 if destination and destination ~= "" then
957 url = url .. "#" .. destination
958 end
959 return pdfdictionary {
960 S = pdf_uri,
961 URI = url,
962 }
963end
964
965local function pdflaunch(program,parameters)
966 if not program or program == "" then
967 return false
968 end
969 return pdfdictionary {
970 S = pdf_launch,
971 F = program,
972 D = ".",
973 P = parameters ~= "" and parameters or nil
974 }
975end
976
977local function pdfjavascript(name,arguments)
978 local script = javascriptcode(name,arguments)
979 if script then
980 return pdfdictionary {
981 S = pdf_javascript,
982 JS = script,
983 }
984 end
985end
986
987
988
989
990
991
992
993
994
995
996local function pdfaction(actions)
997 local nofactions = #actions
998 if nofactions > 0 then
999 local a = actions[1]
1000 local action = runners[a.kind]
1001 if action then
1002 action = action(a,actions)
1003 end
1004 if action then
1005 local first = action
1006 for i=2,nofactions do
1007 local a = actions[i]
1008 local what = runners[a.kind]
1009 if what then
1010 what = what(a,actions)
1011 end
1012 if action == what then
1013
1014 elseif what then
1015 action.Next = what
1016 action = what
1017 else
1018
1019 return nil
1020 end
1021 end
1022 return first, actions.n or #actions
1023 end
1024 end
1025end
1026
1027lpdf.action = pdfaction
1028
1029
1030
1031
1032local nofhighlights = 0
1033local fixhighlight = false
1034
1035directives.register("backend.pdf.fixhighlight", function(v)
1036 fixhighlight = v
1037end)
1038
1039function codeinjections.prerollreference(actions,index)
1040 if actions then
1041 local action = actions[1]
1042 if action and action.special == "highlight" then
1043 nofhighlights = nofhighlights + 1
1044
1045 local author = action.operation
1046 local contents = action.arguments
1047 local color = pdfcolorarray(author and "pdfhighlight:" .. author or nil)
1048 local bs, bc = pdfborder(actions)
1049
1050 local main = pdfdictionary {
1051 Subtype = pdf_highlight,
1052 Border = pdfshareobjectreference(bs),
1053 C = color or pdfarray { .8, .8, .8 },
1054 T = author and pdfunicode(author) or nil,
1055 Contents = contents and contents ~= "" and pdfunicode(contents) or nil,
1056 F = 4,
1057
1058 NM = pdfstring("LMTX:" .. nofhighlights),
1059 LMTX_QP1243 = fixhighlight and true or nil,
1060 }
1061 actions.forcemesh = true
1062 return main, 1
1063 else
1064 local main, n = pdfaction(actions)
1065 if main then
1066 local bs, bc = pdfborder(actions)
1067 main = pdfdictionary {
1068 Subtype = pdf_link,
1069 Border = pdfshareobjectreference(bs),
1070 C = bc,
1071 H = (not actions.highlight and pdf_n) or nil,
1072 A = pdfshareobjectreference(main),
1073 F = 4,
1074 }
1075 return main, n
1076 end
1077 end
1078 end
1079end
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101local hashed = { }
1102local nofunique = 0
1103local nofused = 0
1104local nofspecial = 0
1105local share = true
1106
1107local refobjects = { }
1108
1109local f_annot = formatters["<< /Type /Annot %s /Rect [ %.6N %.6N %.6N %.6N ] >>"]
1110local f_quadp = formatters["<< /Type /Annot %s /QuadPoints [ %s ] /Rect [ %.6N %.6N %.6N %.6N ] >>"]
1111
1112directives.register("references.sharelinks", function(v)
1113 share = v
1114end)
1115
1116setmetatableindex(hashed,function(t,k)
1117 local v = pdfdelayedobject(k)
1118 if share then
1119 t[k] = v
1120 end
1121 nofunique = nofunique + 1
1122 return v
1123end)
1124
1125local function toquadpoints(paths,bugged)
1126 local t, n = { }, 0
1127 if bugged then
1128
1129
1130
1131
1132
1133
1134 for i=1,#paths do
1135 local p = paths[i]
1136 p[3], p[4] = p[4], p[3]
1137 end
1138 end
1139
1140 for i=1,#paths do
1141 local path = paths[i]
1142 local size = #path
1143 for j=1,size do
1144 local p = path[j]
1145 n = n + 1 ; t[n] = p[1]
1146 n = n + 1 ; t[n] = p[2]
1147 end
1148 local m = size % 4
1149 if m > 0 then
1150 local p = path[size]
1151 for j=size+1,m do
1152 n = n + 1 ; t[n] = p[1]
1153 n = n + 1 ; t[n] = p[2]
1154 end
1155 end
1156 end
1157 return concat(t," ")
1158end
1159
1160local finishreference do
1161
1162
1163
1164
1165 local collected = allocate()
1166 local tobesaved = allocate()
1167
1168 local jobmeshedup = {
1169 collected = collected,
1170 tobesaved = tobesaved,
1171 }
1172
1173 job.meshedup = jobmeshedup
1174
1175 local function initializer()
1176 tobesaved = jobmeshedup.tobesaved
1177 collected = jobmeshedup.collected
1178 end
1179
1180 job.register('job.meshedup.collected',tobesaved,initializer)
1181
1182 local checkmesh = false
1183 local lastprerolled = false
1184 local lastmeshedup = false
1185 local lastrealpage = 0
1186
1187 local count = 0
1188 local total = 0
1189 local valid = 0
1190 local more = 0
1191
1192 local function meshup(prerolled,llx,lly,urx,ury)
1193 local q = nil
1194 local r = texgetcount(c_realpageno)
1195 local p = collected[r] and collected[r][prerolled]
1196 if r ~= lastrealpage then
1197 lastrealpage = r
1198 lastprerolled = false
1199 lastmeshedup = false
1200 end
1201 if prerolled == lastprerolled then
1202 if not more then
1203 valid = valid + 1
1204 more = true
1205 end
1206 total = total + 1
1207 if #lastmeshed == 4 then
1208 local t = tobesaved[r]
1209 if not t then
1210 t = { }
1211 tobesaved[r] = t
1212 end
1213 t[prerolled] = lastmeshed
1214 end
1215 lastmeshed[#lastmeshed+1] = llx
1216 lastmeshed[#lastmeshed+1] = lly
1217 lastmeshed[#lastmeshed+1] = urx
1218 lastmeshed[#lastmeshed+1] = ury
1219 else
1220 lastprerolled = prerolled
1221 lastmeshed = { llx, lly, urx, ury }
1222 count = count + 1
1223 more = false
1224 end
1225 if p then
1226 if p.done then
1227 q = true
1228 else
1229 q = { }
1230 if #p > 0 then
1231 for i=1,#p,4 do
1232 local lx = p[i]
1233 local ly = p[i+1]
1234 local ux = p[i+2]
1235 local uy = p[i+3]
1236 if i == 1 then
1237 llx = min(lx,ux)
1238 lly = min(ly,uy)
1239 urx = max(lx,ux)
1240 ury = max(ly,uy)
1241 else
1242 llx = min(llx,lx,ux)
1243 lly = min(lly,ly,uy)
1244 urx = max(urx,lx,ux)
1245 ury = max(ury,ly,uy)
1246 end
1247 q[#q+1] = { { lx, ly }, { ux, ly }, { ux, uy }, { lx, uy } }
1248 end
1249 end
1250 p.done = true
1251 end
1252 end
1253 return llx, lly, urx, ury, q
1254 end
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333 local function identify(prerolled,llx,lly,urx,ury)
1334 local r = texgetcount(c_realpageno)
1335 if r ~= lastrealpage then
1336 lastrealpage = r
1337 lastprerolled = false
1338 lastmeshedup = false
1339 end
1340 if prerolled == lastprerolled then
1341 if not more then
1342 valid = valid + 1
1343 more = true
1344 end
1345 total = total + 1
1346 else
1347 lastprerolled = prerolled
1348 count = count + 1
1349 more = false
1350 end
1351 return llx, lly, urx, ury, nil
1352 end
1353
1354 statistics.register("meshed up references", function()
1355 if count > 0 then
1356 return format("%i seen, %i valid, %i total",count,valid,total)
1357 else
1358 return nil
1359 end
1360 end)
1361
1362 directives.register("references.meshup", function(v)
1363 checkmesh = (v == "identify" and identify) or (v and meshup) or nil
1364 end)
1365
1366
1367
1368 local function checked(prerolled,specification)
1369 local llx, lly, urx, ury = pdfrectangle(specification.width,specification.height,specification.depth)
1370 local quadpoints = specification.mesh
1371 local forcemesh = specification.forcemesh
1372 local rotated = specification.rotated
1373 if quadpoints then
1374
1375 elseif checkmesh then
1376 llx, lly, urx, ury, quadpoints = checkmesh(prerolled,llx,lly,urx,ury)
1377 if quadpoints == true then
1378 return
1379 end
1380 elseif forcemesh then
1381 llx, lly, urx, ury, quadpoints = meshup(prerolled,llx,lly,urx,ury)
1382 if quadpoints == true then
1383 return
1384 end
1385 end
1386 if forcemesh then
1387 if fixhighlight and specification.prerolled and tostring(specification.prerolled.Subtype) == "/Highlight" then
1388 bugged = true
1389 end
1390 if rotated or not quadpoints then
1391 quadpoints = { { { llx, lly }, { urx, lly }, { urx, ury }, { llx, ury } } }
1392 end
1393 elseif rotated then
1394 local x1, y1, x2, y2, x3, y3, x4, y4, llx, lly, urx, ury = pdfquads(0,llx/factor,urx/factor,ury/factor)
1395 quadpoints = { { { x1, y1 }, { x2, y2 }, { x3, y3 }, { x4, y4 } } }
1396 end
1397 if quadpoints and #quadpoints > 0 then
1398 prerolled = f_quadp(prerolled,toquadpoints(quadpoints,bugged),llx,lly,urx,ury)
1399 else
1400 prerolled = f_annot(prerolled,llx,lly,urx,ury)
1401 end
1402 return prerolled
1403 end
1404
1405 finishreference = function(specification)
1406 local prerolled = specification.prerolled
1407 local refatt = specification.reference
1408 if crappytaggingmode > 0 then
1409 if type(prerolled) ~= "string" then
1410
1411 prerolled.Contents = pdfunicode(specification.description or "link")
1412 prerolled.StructParent = codeinjections.getlinkstructureparent(refatt)
1413 prerolled = prerolled()
1414 end
1415 local specifier = checked(prerolled,specification)
1416 if not specifier then
1417 return
1418 end
1419 local objref = pdfreserveobject()
1420 specification.objref = objref
1421
1422 nofused = nofused + 1
1423 if refatt then
1424 refobjects[refatt] = objref
1425 end
1426 pdfdelayedobject(specifier,objref)
1427 return pdfregisterannotation(objref)
1428 else
1429 if type(prerolled) ~= "string" then
1430 prerolled = prerolled()
1431 end
1432 local specifier = checked(prerolled,specification)
1433 if not specifier then
1434 return
1435 end
1436 local objref = hashed[specifier]
1437 specification.objref = objref
1438 nofused = nofused + 1
1439 if refatt then
1440 refobjects[refatt] = objref
1441 end
1442 return pdfregisterannotation(objref)
1443 end
1444 end
1445
1446end
1447
1448function codeinjections.getrefobj(refatt)
1449 return refobjects[refatt]
1450end
1451
1452local function finishannotation(specification)
1453 local prerolled = specification.prerolled
1454 local objref = specification.objref
1455 if type(prerolled) == "function" then
1456 prerolled = prerolled()
1457 end
1458 if type(prerolled) ~= "string" then
1459 prerolled = prerolled()
1460 end
1461 local annot = f_annot(prerolled,pdfrectangle(specification.width,specification.height,specification.depth))
1462 if objref then
1463 pdfdelayedobject(annot,objref)
1464 else
1465 objref = pdfdelayedobject(annot)
1466 specification.objref = objref
1467 end
1468 nofspecial = nofspecial + 1
1469 return pdfregisterannotation(objref)
1470end
1471
1472function nodeinjections.reference(reference,width,height,depth,prerolled,mesh,description,forcemesh,rotated)
1473 if prerolled then
1474 if trace_references then
1475 report_references("link: width %p, height %p, depth %p, prerolled %a",width,height,depth,prerolled)
1476 end
1477 return new_latelua {
1478
1479 action = finishreference,
1480 reference = reference,
1481 width = width,
1482 height = height,
1483 depth = depth,
1484 prerolled = prerolled,
1485 mesh = mesh,
1486 description = description,
1487 forcemesh = forcemesh,
1488 rotated = rotated,
1489 }
1490 end
1491end
1492
1493function nodeinjections.annotation(width,height,depth,prerolled,objref)
1494 if prerolled then
1495 if trace_references then
1496 report_references("special: width %p, height %p, depth %p, prerolled %a",width,height,depth,
1497 type(prerolled) == "string" and prerolled or "-")
1498 end
1499 return new_latelua {
1500
1501 action = finishannotation,
1502 width = width,
1503 height = height,
1504 depth = depth,
1505 prerolled = prerolled,
1506 objref = objref or false,
1507 }
1508 end
1509end
1510
1511
1512
1513
1514
1515local annotations = nil
1516
1517pdfregisterannotation = function(n)
1518 if annotations then
1519 annotations[#annotations+1] = pdfreference(n)
1520 else
1521 annotations = pdfarray { pdfreference(n) }
1522 end
1523 return n
1524end
1525
1526lpdf.registerannotation = pdfregisterannotation
1527
1528
1529
1530
1531
1532function lpdf.annotationspecification()
1533 if annotations then
1534 local r = pdfdelayedobject(tostring(annotations))
1535 if r then
1536 pdfaddtopageattributes("Annots",pdfreference(r))
1537
1538 pdfaddtopageattributes("Tabs",pdfconstant("S"))
1539
1540 end
1541 annotations = nil
1542 end
1543end
1544
1545lpdf.registerpagefinalizer(lpdf.annotationspecification,"finalize annotations")
1546
1547statistics.register("pdf annotations", function()
1548 if nofused > 0 or nofspecial > 0 then
1549 return format("%s links (%s unique), %s special",nofused,nofunique,nofspecial)
1550 else
1551 return nil
1552 end
1553end)
1554
1555
1556
1557local splitter = lpeg.splitat(",",true)
1558
1559runners["inner"] = function(var,actions)
1560 local internal = false
1561 local name = nil
1562 local method = references.innermethod
1563 local vi = var.i
1564 local page = var.r
1565 if vi then
1566 local vir = vi.references
1567 if vir then
1568
1569
1570 local reference = vir.reference
1571 if reference and reference ~= "" then
1572 reference = lpegmatch(splitter,reference) or reference
1573 var.inner = reference
1574 local prefix = var.p
1575 if prefix and prefix ~= "" then
1576 var.prefix = prefix
1577 name = prefix .. ":" .. reference
1578 else
1579 name = reference
1580 end
1581 end
1582 internal = vir.internal
1583 if internal then
1584 flaginternals[internal] = true
1585 end
1586 end
1587 end
1588 if name then
1589 return pdflinkname(name,internal,page)
1590
1591 elseif internal then
1592 return pdflinkinternal(internal,page,true)
1593 elseif page then
1594 return pdflinkpage(page)
1595 else
1596
1597 end
1598end
1599
1600runners["inner with arguments"] = function(var,actions)
1601 report_references("todo: inner with arguments")
1602 return false
1603end
1604
1605runners["outer"] = function(var,actions)
1606 local file, url = references.checkedfileorurl(var.outer,var.outer)
1607 if file then
1608 return pdffilelink(file,var.arguments,nil,actions)
1609 elseif url then
1610 return pdfurllink(url,var.arguments,nil,actions)
1611 end
1612end
1613
1614runners["outer with inner"] = function(var,actions)
1615 if var.r then
1616 actions.realpage = var.r
1617 end
1618 return pdffilelink(references.checkedfile(var.outer),var.inner,var.r,actions)
1619end
1620
1621runners["special outer with operation"] = function(var,actions)
1622 local handler = specials[var.special]
1623 return handler and handler(var,actions)
1624end
1625
1626runners["special outer"] = function(var,actions)
1627 report_references("todo: special outer")
1628 return false
1629end
1630
1631runners["special"] = function(var,actions)
1632 local handler = specials[var.special]
1633 return handler and handler(var,actions)
1634end
1635
1636runners["outer with inner with arguments"] = function(var,actions)
1637 report_references("todo: outer with inner with arguments")
1638 return false
1639end
1640
1641runners["outer with special and operation and arguments"] = function(var,actions)
1642 report_references("todo: outer with special and operation and arguments")
1643 return false
1644end
1645
1646runners["outer with special"] = function(var,actions)
1647 report_references("todo: outer with special")
1648 return false
1649end
1650
1651runners["outer with special and operation"] = function(var,actions)
1652 report_references("todo: outer with special and operation")
1653 return false
1654end
1655
1656runners["special operation"] = runners["special"]
1657runners["special operation with arguments"] = runners["special"]
1658
1659local reported = { }
1660
1661function specials.internal(var,actions)
1662 local o = var.operation
1663 local i = o and tonumber(o)
1664 local v = i and references.internals[i]
1665 if v then
1666 flaginternals[i] = true
1667 return pdflinkinternal(i,v.references.realpage)
1668 end
1669 local v = i or o or "<unset>"
1670 if not reported[v] then
1671 report_references("no internal reference %a",v)
1672 reported[v] = true
1673 end
1674end
1675
1676
1677
1678specials.i = specials.internal
1679
1680local pages = references.pages
1681
1682function specials.page(var,actions)
1683 local file = var.f
1684 if file then
1685 return pdffilelink(references.checkedfile(file),nil,var.operation,actions)
1686 else
1687 local p = var.r
1688 if not p then
1689 p = pages[var.operation]
1690 if type(p) == "function" then
1691 p = p()
1692 else
1693 p = references.realpageofpage(tonumber(p))
1694 end
1695 end
1696 return pdflinkpage(p or var.operation)
1697 end
1698end
1699
1700function specials.realpage(var,actions)
1701 local file = var.f
1702 if file then
1703 return pdffilelink(references.checkedfile(file),nil,var.operation,actions)
1704 else
1705 return pdflinkpage(var.operation)
1706 end
1707end
1708
1709function specials.userpage(var,actions)
1710 local file = var.f
1711 if file then
1712 return pdffilelink(references.checkedfile(file),nil,var.operation,actions)
1713 else
1714 local p = var.r
1715 if not p then
1716 p = var.operation
1717 if p then
1718 p = references.realpageofpage(tonumber(p))
1719 end
1720
1721
1722
1723 end
1724 return pdflinkpage(p or var.operation)
1725 end
1726end
1727
1728function specials.deltapage(var,actions)
1729 local p = tonumber(var.operation)
1730 if p then
1731 p = references.checkedrealpage(p + texgetcount(c_realpageno))
1732 return pdflinkpage(p)
1733 end
1734end
1735
1736
1737
1738function specials.section(var,actions)
1739
1740 local sectionname = var.arguments
1741 local destination = var.operation
1742 local internal = structures.sections.internalreference(sectionname,destination)
1743 if internal then
1744 var.special = "internal"
1745 var.operation = internal
1746 var.arguments = nil
1747 return specials.internal(var,actions)
1748 end
1749end
1750
1751
1752
1753local splitter = lpeg.splitat(":")
1754
1755function specials.order(var,actions)
1756 local operation = var.operation
1757 if operation then
1758 local kind, name, n = lpegmatch(splitter,operation)
1759 local order = structures.lists.ordered[kind]
1760 order = order and order[name]
1761 local v = order[tonumber(n)]
1762 local r = v and v.references.realpage
1763 if r then
1764 var.operation = r
1765 return specials.page(var,actions)
1766 end
1767 end
1768end
1769
1770function specials.url(var,actions)
1771 return pdfurllink(references.checkedurl(var.operation),var.arguments,nil,actions)
1772end
1773
1774function specials.file(var,actions)
1775 return pdffilelink(references.checkedfile(var.operation),var.arguments,nil,actions)
1776end
1777
1778function specials.fileorurl(var,actions)
1779 local file, url = references.checkedfileorurl(var.operation,var.operation)
1780 if file then
1781 return pdffilelink(file,var.arguments,nil,actions)
1782 elseif url then
1783 return pdfurllink(url,var.arguments,nil,actions)
1784 end
1785end
1786
1787function specials.program(var,content)
1788 local program = references.checkedprogram(var.operation)
1789 return pdflaunch(program,var.arguments)
1790end
1791
1792function specials.javascript(var)
1793 return pdfjavascript(var.operation,var.arguments)
1794end
1795
1796specials.JS = specials.javascript
1797
1798function specials.highlight(var)
1799
1800
1801end
1802
1803executers.importform = pdfdictionary { S = pdf_named, N = pdfconstant("AcroForm:ImportFDF") }
1804executers.exportform = pdfdictionary { S = pdf_named, N = pdfconstant("AcroForm:ExportFDF") }
1805executers.first = pdfdictionary { S = pdf_named, N = pdfconstant("FirstPage") }
1806executers.previous = pdfdictionary { S = pdf_named, N = pdfconstant("PrevPage") }
1807executers.next = pdfdictionary { S = pdf_named, N = pdfconstant("NextPage") }
1808executers.last = pdfdictionary { S = pdf_named, N = pdfconstant("LastPage") }
1809executers.backward = pdfdictionary { S = pdf_named, N = pdfconstant("GoBack") }
1810executers.forward = pdfdictionary { S = pdf_named, N = pdfconstant("GoForward") }
1811executers.print = pdfdictionary { S = pdf_named, N = pdfconstant("Print") }
1812executers.exit = pdfdictionary { S = pdf_named, N = pdfconstant("Quit") }
1813executers.close = pdfdictionary { S = pdf_named, N = pdfconstant("Close") }
1814executers.save = pdfdictionary { S = pdf_named, N = pdfconstant("Save") }
1815executers.savenamed = pdfdictionary { S = pdf_named, N = pdfconstant("SaveAs") }
1816executers.opennamed = pdfdictionary { S = pdf_named, N = pdfconstant("Open") }
1817executers.help = pdfdictionary { S = pdf_named, N = pdfconstant("HelpUserGuide") }
1818executers.toggle = pdfdictionary { S = pdf_named, N = pdfconstant("FullScreen") }
1819executers.search = pdfdictionary { S = pdf_named, N = pdfconstant("Find") }
1820executers.searchagain = pdfdictionary { S = pdf_named, N = pdfconstant("FindAgain") }
1821executers.gotopage = pdfdictionary { S = pdf_named, N = pdfconstant("GoToPage") }
1822executers.query = pdfdictionary { S = pdf_named, N = pdfconstant("AcroSrch:Query") }
1823executers.queryagain = pdfdictionary { S = pdf_named, N = pdfconstant("AcroSrch:NextHit") }
1824executers.fitwidth = pdfdictionary { S = pdf_named, N = pdfconstant("FitWidth") }
1825executers.fitheight = pdfdictionary { S = pdf_named, N = pdfconstant("FitHeight") }
1826
1827local function fieldset(arguments)
1828
1829 return nil
1830end
1831
1832function executers.resetform(arguments)
1833 arguments = (type(arguments) == "table" and arguments) or settings_to_array(arguments)
1834 return pdfdictionary {
1835 S = pdfconstant("ResetForm"),
1836 Field = fieldset(arguments[1])
1837 }
1838end
1839
1840local formmethod = "post"
1841local formformat = "xml"
1842
1843
1844
1845local flags = {
1846 get = {
1847 html = 12, fdf = 8, xml = 40,
1848 },
1849 post = {
1850 html = 4, fdf = 0, xml = 32,
1851 }
1852}
1853
1854function executers.submitform(arguments)
1855 arguments = (type(arguments) == "table" and arguments) or settings_to_array(arguments)
1856 local flag = flags[formmethod] or flags.post
1857 flag = (flag and (flag[formformat] or flag.xml)) or 32
1858 return pdfdictionary {
1859 S = pdfconstant("SubmitForm"),
1860 F = arguments[1],
1861 Field = fieldset(arguments[2]),
1862 Flags = flag,
1863
1864 }
1865end
1866
1867local pdf_hide = pdfconstant("Hide")
1868
1869function executers.hide(arguments)
1870 return pdfdictionary {
1871 S = pdf_hide,
1872 H = true,
1873 T = arguments,
1874 }
1875end
1876
1877function executers.show(arguments)
1878 return pdfdictionary {
1879 S = pdf_hide,
1880 H = false,
1881 T = arguments,
1882 }
1883end
1884
1885function specials.action(var)
1886 local operation = var.operation
1887 if var.operation and operation ~= "" then
1888 local e = executers[operation]
1889 if type(e) == "table" then
1890 return e
1891 elseif type(e) == "function" then
1892 return e(var.arguments)
1893 end
1894 end
1895end
1896
1897local function build(levels,start,parent,method,nested)
1898 local startlevel = levels[start].level
1899 local noflevels = #levels
1900 local i = start
1901 local n = 0
1902 local child, entry, m, prev, first, last, f, l
1903 while i and i <= noflevels do
1904 local current = levels[i]
1905 if current.usedpage == false then
1906
1907 i = i + 1
1908 else
1909 local level = current.level
1910 local title = current.title
1911 local reference = current.reference
1912 local opened = current.opened
1913 local reftype = type(reference)
1914 local block = nil
1915 local variant = "unknown"
1916 if reftype == "table" then
1917
1918 variant = "list"
1919 block = reference.block
1920 realpage = reference.realpage
1921 elseif reftype == "string" then
1922 local resolved = references.identify("",reference)
1923 realpage = resolved and structures.references.setreferencerealpage(resolved) or 0
1924 if realpage > 0 then
1925 variant = "realpage"
1926 realpage = realpage
1927 reference = structures.pages.collected[realpage]
1928 block = reference and reference.block
1929 end
1930 elseif reftype == "number" then
1931 if reference > 0 then
1932 variant = "realpage"
1933 realpage = reference
1934 reference = structures.pages.collected[realpage]
1935 block = reference and reference.block
1936 end
1937 else
1938
1939 end
1940 current.block = block
1941 if variant == "unknown" then
1942
1943 i = i + 1
1944
1945 elseif (level < startlevel) or (i > 1 and block ~= levels[i-1].block) then
1946 if nested then
1947 if entry then
1948 pdfflushobject(child,entry)
1949 else
1950 report_bookmarks("error 1")
1951 end
1952 return i, n, first, last
1953 else
1954 if i > 1 and block == levels[i-1].block then
1955
1956 report_bookmarks("confusing level change at level %a around %a",level,title)
1957 end
1958 startlevel = level
1959 end
1960 end
1961 if level == startlevel then
1962 if trace_bookmarks then
1963 report_bookmarks("%3i %w%s %s",realpage,(level-1)*2,(opened and "+") or "-",title)
1964 end
1965 local prev = child
1966 child = pdfreserveobject()
1967 if entry then
1968 entry.Next = child and pdfreference(child)
1969 pdfflushobject(prev,entry)
1970 end
1971 local action = nil
1972 if variant == "list" then
1973 action = pdflinkinternal(reference.internal,reference.realpage)
1974 elseif variant == "realpage" then
1975 action = pagereferences[realpage]
1976 else
1977
1978 end
1979 entry = pdfdictionary {
1980 Title = pdfunicode(title),
1981 Parent = parent,
1982 Prev = prev and pdfreference(prev),
1983 A = action,
1984 }
1985
1986 if not first then
1987 first, last = child, child
1988 end
1989 prev = child
1990 last = prev
1991 n = n + 1
1992 i = i + 1
1993 elseif i < noflevels and level > startlevel then
1994 i, m, f, l = build(levels,i,pdfreference(child),method,true)
1995 if entry then
1996 entry.Count = (opened and m) or -m
1997 if m > 0 then
1998 entry.First = pdfreference(f)
1999 entry.Last = pdfreference(l)
2000 end
2001 else
2002 report_bookmarks("error 2")
2003 end
2004 else
2005
2006 i, m, f, l = build(levels,i,pdfreference(child),method,true)
2007 if entry then
2008 entry.Count = (opened and m) or -m
2009 if m > 0 then
2010 entry.First = pdfreference(f)
2011 entry.Last = pdfreference(l)
2012 end
2013 pdfflushobject(child,entry)
2014 else
2015 report_bookmarks("error 3")
2016 end
2017 return i, n, first, last
2018 end
2019 end
2020 end
2021 pdfflushobject(child,entry)
2022 return nil, n, first, last
2023end
2024
2025function codeinjections.addbookmarks(levels,method)
2026 if levels and #levels > 0 then
2027 local parent = pdfreserveobject()
2028 local _, m, first, last = build(levels,1,pdfreference(parent),method or "internal",false)
2029 local dict = pdfdictionary {
2030 Type = pdfconstant("Outlines"),
2031 First = pdfreference(first),
2032 Last = pdfreference(last),
2033 Count = m,
2034 }
2035 pdfflushobject(parent,dict)
2036 pdfaddtocatalog("Outlines",lpdf.reference(parent))
2037 end
2038end
2039
2040
2041
2042lpdf.registerdocumentfinalizer(function() bookmarks.place() end,1,"bookmarks")
2043 |