node-ref.lmt /size: 31 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['node-ref'] = {
2    version   = 1.001,
3    optimize  = true,
4    comment   = "companion to node-ref.mkiv",
5    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
6    copyright = "PRAGMA ADE / ConTeXt Development Team",
7    license   = "see context related readme files"
8}
9
10-- We supported pdf right from the start and in mkii this has resulted in extensive
11-- control over the links. Nowadays pdftex provides a lot more control over margins
12-- but as mkii supports multiple backends we stuck to our own mechanisms. In mkiv
13-- again we implement our own handling. Eventually we will even disable the pdf
14-- primitives.
15
16local tonumber = tonumber
17local concat = table.concat
18
19local attributes, nodes, node = attributes, nodes, node
20
21local allocate             = utilities.storage.allocate, utilities.storage.mark
22local mark                 = utilities.storage.allocate, utilities.storage.mark
23
24local nodeinjections       = backends.nodeinjections
25local codeinjections       = backends.codeinjections
26
27local cleanupreferences    = false
28local cleanupdestinations  = true
29
30local transparencies       = attributes.transparencies
31local colors               = attributes.colors
32local references           = structures.references
33local enableaction         = nodes.tasks.enableaction
34
35local trace_references     = false  trackers.register("nodes.references",        function(v) trace_references   = v end)
36local trace_destinations   = false  trackers.register("nodes.destinations",      function(v) trace_destinations = v end)
37local trace_areas          = false  trackers.register("nodes.areas",             function(v) trace_areas        = v end)
38local show_references      = false  trackers.register("nodes.references.show",   function(v) show_references    = tonumber(v) or (v and 2.25 or false) end)
39local show_destinations    = false  trackers.register("nodes.destinations.show", function(v) show_destinations  = tonumber(v) or (v and 2.00 or false) end)
40
41local report_reference     = logs.reporter("backend","references")
42local report_destination   = logs.reporter("backend","destinations")
43local report_area          = logs.reporter("backend","areas")
44
45local texiscount           = tex.iscount
46local texsetcount          = tex.setcount
47----- texsetattribute      = tex.setattribute
48
49local injectreference      = backends.nodeinjections.reference
50local injectdestination    = backends.nodeinjections.destination
51local prerollreference     = backends.codeinjections.prerollreference
52
53updaters.register("backends.injections.latebindings",function()
54    injectreference   = backends.nodeinjections.reference
55    injectdestination = backends.nodeinjections.destination
56    prerollreference  = backends.codeinjections.prerollreference
57end)
58
59local c_lastreferenceattribute = texiscount("lastreferenceattribute")
60
61local nuts                 = nodes.nuts
62local nodepool             = nuts.pool
63
64local tonode               = nuts.tonode
65local tonut                = nuts.tonut
66
67local setlink              = nuts.setlink
68local setnext              = nuts.setnext
69local setprev              = nuts.setprev
70local getnext              = nuts.getnext
71local getprev              = nuts.getprev
72local getid                = nuts.getid
73local getlist              = nuts.getlist
74local setlist              = nuts.setlist
75local getwidth             = nuts.getwidth
76local setwidth             = nuts.setwidth
77local getheight            = nuts.getheight
78local getattr              = nuts.getattr
79local setattr              = nuts.setattr
80local getsubtype           = nuts.getsubtype
81local getwhd               = nuts.getwhd
82local getdirection         = nuts.getdirection
83local setshift             = nuts.setshift
84local getboxglue           = nuts.getboxglue
85
86local hpack_list           = nuts.hpack
87local vpack_list           = nuts.vpack
88local getdimensions        = nuts.dimensions
89local getrangedimensions   = nuts.rangedimensions
90local traverse             = nuts.traverse
91local find_node_tail       = nuts.tail
92
93local startofpar           = nuts.startofpar
94
95local nodecodes            = nodes.nodecodes
96local gluecodes            = nodes.gluecodes
97local listcodes            = nodes.listcodes
98
99local directioncodes       = tex.directioncodes
100local lefttoright_code     = directioncodes.lefttoright
101local righttoleft_code     = directioncodes.righttoleft
102
103local hlist_code           = nodecodes.hlist
104local vlist_code           = nodecodes.vlist
105local glue_code            = nodecodes.glue
106local glyph_code           = nodecodes.glyph
107local rule_code            = nodecodes.rule
108local dir_code             = nodecodes.dir
109local par_code             = nodecodes.par
110
111local leftskip_code        = gluecodes.leftskip
112local lefthang_code        = gluecodes.lefthangskip
113local rightskip_code       = gluecodes.rightskip
114local righthang_code       = gluecodes.righthangskip
115local parfillleftskip_code = gluecodes.parfillleftskip
116local parfillskip_code     = gluecodes.parfillskip
117
118----- linelist_code        = listcodes.line
119
120local new_rule             = nodepool.rule
121local new_kern             = nodepool.kern
122local new_hlist            = nodepool.hlist
123
124local flushnode            = nuts.flush
125
126local tosequence           = nodes.tosequence
127
128local implement            = interfaces.implement
129
130-- Normally a (destination) area is a box or a simple stretch if nodes but when it is
131-- a paragraph we have a problem: we cannot calculate the height well. This happens
132-- with footnotes or content broken across a page.
133
134-- skip is somewhat messy
135-- when we have a line line we can look at the next line and make a shape
136-- see dimensions: this is tricky with split off boxes like inserts
137-- where we can end up with a first and last spanning lines so maybe
138-- we need to do vlists differently
139-- no need for dir here if we inject in the right spot as then we can
140-- pick up the dir elsewhere (in lmtx)
141
142local inject_areas do
143
144    -- This one is sensitive for the fourth argument (ns) so I need to add it as option
145    -- instead.
146
147    local function hlist_dimensions(start,stop,parent)
148        local last = stop and getnext(stop)
149        if parent then
150            return getrangedimensions(parent,start,last)
151        else
152            return getdimensions(start,last)
153        end
154    end
155
156    local function vlist_dimensions(start,stop) -- also needs the stretch and so
157        local temp
158        if stop then
159            temp = getnext(stop)
160            setnext(stop,nil)
161        end
162        local v = vpack_list(start) -- use helper but happens seldom anyway so ...
163        local w, h, d = getwhd(v)
164        setlist(v) -- not needed
165        flushnode(v)
166        if temp then
167            setnext(stop,temp)
168        end
169        return w, h, d
170    end
171
172    -- not ok when vlist at mvl level
173
174    local function dimensions(parent,start,stop) -- in principle we could move some to the caller
175        if start == stop then
176            local id = getid(start)
177            if id == hlist_code or id == vlist_code or id == rule_code or id == glyph_code then
178                local sw, sh, sd = getwhd(start)
179                local pw, ph, pd = getwhd(parent)
180                local ht = sh == 0 and ph or sh -- changed
181                local dp = sd == 0 and pd or sd -- changed
182                if trace_areas then
183                    report_area("dimensions taken of %a (%p,%p,%p) with parent (%p,%p,%p) -> (%p,%p,%p)",
184                        nodecodes[id],sw,sh,sd,pw,ph,pd,sw,ht,dp)
185                end
186                return sw, ht, dp
187            else
188                if trace_areas then
189                    report_area("dimensions calculated of %a",nodecodes[id])
190                end
191                return hlist_dimensions(start,stop) -- one node only so simple
192            end
193        elseif parent then
194            -- todo: if no prev and no next and parent
195            -- todo: we need a a list_dimensions for a vlist
196            if getid(parent) == vlist_code then
197                --
198                -- we can as well calculate here because we only have kerns and glue
199                --
200                local last     = stop and getnext(stop)
201                local first    = nil
202                local last     = nil
203                local current  = start
204                local noflines = 0
205                while current do -- can be loop
206                    local id = getid(current)
207                    if id == hlist_code or id == vlist_code or id == rule_code then
208                        if noflines == 0 then
209                            first    = current
210                            noflines = 1
211                        else
212                            noflines = noflines + 1
213                        end
214                        last = current
215                    end
216                    if current == stop then
217                        break
218                    else
219                        current = getnext(current)
220                    end
221                end
222                if noflines > 1 then
223                    if trace_areas then
224                        report_area("dimensions taken of vlist")
225                    end
226                    local w, h, d = vlist_dimensions(first,last,parent)
227                    local ht = getheight(first)
228                    return w, ht, d + h - ht, first
229                elseif first then
230                    if trace_areas then
231                        report_area("dimensions taken of first line in vlist")
232                    end
233                    local w, h, d = getwhd(first)
234                    return w, h, d, first
235                else
236                    if trace_areas then
237                        report_area("dimensions taken of vlist (probably wrong)")
238                    end
239                    return hlist_dimensions(start,stop,parent)
240                end
241            else
242                if trace_areas then
243                    report_area("dimensions taken of range starting with %a using parent",nodecodes[getid(start)])
244                end
245                return hlist_dimensions(start,stop,parent)
246            end
247        else
248            if trace_areas then
249                report_area("dimensions taken of range starting with %a",nodecodes[getid(start)])
250            end
251            return hlist_dimensions(start,stop)
252        end
253    end
254
255    -- Setting these once and not passing them each nested call is faster and this injector is
256    -- actually one of the more costly calls in a run (when we have lots of references to
257    -- check) and it's also a bti less code.
258
259    local attribute, make, stack, done
260
261    local function inject_range(head,first,last,reference,parent,pardir,txtdir)
262        local width, height, depth, line = dimensions(parent,first,last)
263        if txtdir == righttoleft_code then
264            width = - width
265        elseif txtdir == lefttoright_code then
266            -- go on
267        elseif pardir == righttoleft_code then
268            width = - width
269        end
270        local result, resolved = make(width,height,depth,reference)
271        if result and resolved then
272            if line then
273                -- special case, we only treat the first line in a vlist
274                local l = getlist(line)
275                if trace_areas then
276                    report_area("%s: %i : %s %s %s => w=%p, h=%p, d=%p","line",
277                        reference,pardir or "?",txtdir or "?",
278                        tosequence(l,nil,true),width,height,depth)
279                end
280                setlist(line,result)
281                setlink(result,l)
282                return head, last
283            elseif head == first then
284                if trace_areas then
285                    report_area("%s: %i : %s %s %s => w=%p, h=%p, d=%p","head",
286                        reference,pardir or "?",txtdir or "?",
287                        tosequence(first,last,true),width,height,depth)
288                end
289                setlink(result,first)
290                return result, last
291            else
292                if trace_areas then
293                    report_area("%s: %i : %s %s %s => w=%p, h=%p, d=%p","middle",
294                        reference,pardir or "?",txtdir or "?",
295                        tosequence(first,last,true),width,height,depth)
296                end
297                if first == last and getid(parent) == vlist_code and getid(first) == hlist_code then
298                    if trace_areas then
299                        -- think of a button without \dontleavehmode in the mvl
300                        report_area("compensating for link in vlist")
301                    end
302                    setlink(result,getlist(first))
303                    setlist(first,result)
304                else
305                    setlink(getprev(first),result,first)
306                end
307                return head, last
308            end
309        else
310            return head, last
311        end
312    end
313
314    -- Somehow this one no longer got used. See node-ref.lua for the implementation.
315    --
316    -- local function inject_list(id,current,reference,pardir,txtdir)
317    -- end
318
319    local function inject(head,skip,parent,pardir,txtdir)
320        local first, last, firstdir, reference
321        local current = head
322        while current do -- tod: check with loop
323            local id = getid(current)
324            if id == hlist_code or id == vlist_code then
325                local r = getattr(current,attribute)
326                -- test \goto{test}[page(2)] test \gotobox{test}[page(2)]
327                -- test \goto{\TeX}[page(2)] test \gotobox{\hbox {x} \hbox {x}}[page(2)]
328                if r then
329                    if not reference then
330                        reference = r
331                        first     = current
332                        last      = current
333                        firstdir  = txtdir
334                    elseif r == reference then
335                        -- same link
336                        last = current
337                    elseif (done[reference] or 0) == 0 then
338                        if not skip or r > skip then -- maybe no > test
339                            head, current = inject_range(head,first,last,reference,parent,pardir,firstdir)
340                            reference = nil
341                            first     = nil
342                            last      = nil
343                            firstdir  = nil
344                        end
345                    else
346                        reference = r
347                        first     = current
348                        last      = current
349                        firstdir  = txtdir
350                    end
351                    done[r] = (done[r] or 0) + 1
352                end
353                local list = getlist(current)
354                if list then
355                    local h
356                    h, pardir, txtdir = inject(list,r or skip or 0,current,pardir,txtdir)
357                    if h ~= current then
358                        setlist(current,h)
359                    end
360                end
361                if r then
362                    done[r] = done[r] - 1
363                end
364                goto NEXT
365            elseif id == dir_code then
366                local direction, pop = getdirection(current)
367                txtdir = not pop and direction -- we might need a stack
368                goto NEXT
369            elseif id == par_code then
370                if startofpar(current) then
371                    pardir = getdirection(current)
372                end
373                goto NEXT
374            elseif id == glue_code then
375                -- local skiptype = ... 0=regular 1=left 2=right
376                local subtype = getsubtype(current)
377                if subtype == leftskip_code or subtype == lefthang_code or subtype == parfillleftskip_code then
378                    goto NEXT
379                elseif subtype == rightskip_code or subtype == righthang_code or subtype == parfillskip_code then
380                    if reference and (done[reference] or 0) == 0 then
381                        head, current = inject_range(head,first,last,reference,parent,pardir,firstdir)
382                        reference = nil
383                        first     = nil
384                        last      = nil
385                        firstdir  = nil
386                    end
387                    goto NEXT
388                end
389            end
390            do
391                local r = getattr(current,attribute)
392                if not r then
393                    -- just go on, can be kerns
394                elseif not reference then
395                    reference = r
396                    first     = current
397                    last      = current
398                    firstdir  = txtdir
399                elseif r == reference then
400                    last = current
401                elseif (done[reference] or 0) == 0 then -- or (id == glue_code and getsubtype(current) == right_skip_code) then
402                    if not skip or r > skip then -- maybe no > test
403                        head, current = inject_range(head,first,last,reference,parent,pardir,firstdir)
404                        reference = nil
405                        first     = nil
406                        last      = nil
407                        firstdir  = nil
408                    else
409--                         print("!!!!!!!!")
410                    end
411                else
412                    -- todo: safeguard when we have a glyph or disc or ... so we might as well
413                    -- then scan for all possible content nodes
414                    reference = r
415                    first     = current
416                    last      = current
417                    firstdir  = txtdir
418                end
419            end
420          ::NEXT::
421            current = getnext(current)
422        end
423        if reference and (done[reference] or 0) == 0 then
424            head = inject_range(head,first,last,reference,parent,pardir,firstdir)
425        end
426        return head, pardir, txtdir
427    end
428
429    inject_areas = function(head,a,m,s,d)
430        attribute = a
431        make      = m
432        stack     = s
433        done      = d
434        return (inject(head))
435    end
436
437end
438
439-- tracing: todo: use predefined colors
440
441local colorize, justadd do
442
443    local u_transparency = nil
444    local u_colors       = { }
445    local force_gray     = true
446
447    local register_color = colors.register
448
449    local setcoloring    = nuts.colors.set
450
451    local function addstring(what,str,shift) --todo make a pluggable helper (in font-ctx)
452        if str then
453            local typesetters = nuts.typesetters
454            if typesetters then
455                local hashes   = fonts.hashes
456                local infofont = fonts.infofont(true) -- small
457                local emwidth  = hashes.emwidths [infofont]
458                local exheight = hashes.exheights[infofont]
459                if what == "reference" then
460                    str   = str .. " "
461                    shift = - (shift or 2.25) * exheight
462                else
463                    str   = str .. " "
464                    shift = (shift or 2) * exheight
465                end
466                -- todo: virtual rule, zero width hbox, offsets
467                local text = typesetters.tohpack(str,infofont)
468                local rule = new_rule(emwidth/5,4*exheight,3*exheight)
469                setshift(text,shift)
470                return hpack_list(setlink(text,rule))
471            end
472        end
473    end
474
475    colorize = function(width,height,depth,n,reference,what,sr,offset)
476        if force_gray then
477            n = 0
478        end
479        u_transparency = u_transparency or transparencies.register(nil,2,.65)
480        local ucolor = u_colors[n]
481        if not ucolor then
482            if n == 1 then
483                u_color = register_color(nil,'rgb',.75,0,0)
484            elseif n == 2 then
485                u_color = register_color(nil,'rgb',0,.75,0)
486            elseif n == 3 then
487                u_color = register_color(nil,'rgb',0,0,.75)
488            else
489                n = 0
490                u_color = register_color(nil,'gray',.5)
491            end
492            u_colors[n] = u_color
493        end
494        if width == 0 then
495            -- probably a strut as placeholder
496            report_area("%s %s has no %s dimensions, width %p, height %p, depth %p",what,reference,"horizontal",width,height,depth)
497            width = 65536
498        end
499        if height + depth <= 0 then
500            report_area("%s %s has no %s dimensions, width %p, height %p, depth %p",what,reference,"vertical",width,height,depth)
501            height = 65536/2
502            depth  = height
503        end
504        -- todo: use virtual rules
505        local rule = setcoloring(new_rule(width,height,depth),1,u_color,u_transparency) -- gray color model
506        if width < 0 then
507            local kern = new_kern(width)
508            setwidth(rule,-width)
509            setlink(kern,rule)
510            return kern
511        elseif sr and sr ~= "" then
512            local text = addstring(what,sr,shift)
513            if text then
514                local kern = new_kern(-getwidth(text))
515                setlink(kern,text,rule)
516                return kern
517            end
518        end
519        return rule
520    end
521
522    justadd = function(what,sr,shift,current) -- needs testing
523        if sr and sr ~= "" then
524            local text = addstring(what,sr,shift)
525            if text then
526                local kern = new_kern(-getwidth(text))
527                setlink(kern,text,current)
528                return new_hlist(kern)
529            end
530        end
531    end
532
533end
534
535local nofreferences = 0
536
537do
538
539    -- here we can't use attributes.registervalue etc (afaiks)
540
541    local stack      = { }
542    local done       = { }
543    local attribute  = attributes.private('reference')
544    local topofstack = 0
545
546    nodes.references = {
547        attribute = attribute,
548        stack     = stack,
549        done      = done,
550    }
551
552    -- todo: get rid of n (n is just a number, can be used for tracing, obsolete)
553
554    local function setreference(h,d,r) -- h and d can be nil
555        topofstack = topofstack + 1
556        -- the preroll permits us to determine samepage (but delayed also has some advantages)
557        -- so some part of the backend work is already done here
558        stack[topofstack] = { r, h or false, d or false, codeinjections.prerollreference(r) }
559     -- texsetattribute(attribute,topofstack) -- todo -> at tex end
560        texsetcount(c_lastreferenceattribute,topofstack)
561    end
562
563    function references.get(n) -- not public so functionality can change
564        local sn = stack[n]
565        return sn and sn[1]
566    end
567
568    local function makereference(width,height,depth,reference) -- height and depth are of parent
569        local sr = stack[reference]
570        if sr then
571            if trace_references then
572                report_reference("resolving attribute %a",reference)
573            end
574            local resolved = sr[1]
575            local ht       = sr[2]
576            local dp       = sr[3]
577            local set      = sr[4]
578            local n        = sr[5]
579         -- logs.report("temp","child: ht=%p dp=%p, parent: ht=%p dp=%p",ht,dp,height,depth)
580            if ht then
581                if height < ht then height = ht end
582                if depth  < dp then depth  = dp end
583            end
584         -- logs.report("temp","used: ht=%p dp=%p",height,depth)
585            local annot = injectreference(reference,width,height,depth,set,resolved.mesh)
586            if annot then
587                annot = tonut(annot) -- todo
588                nofreferences = nofreferences + 1
589                local result, current, texts
590                if show_references then
591                    local d = resolved
592                    if d then
593                        local r = d.reference
594                        local p = d.prefix
595                        if r then
596                            if p then
597                                texts = p .. "|" .. r
598                            else
599                                texts = r
600                            end
601                        else
602                         -- t[#t+1] = d.internal or "?"
603                        end
604                    end
605                end
606                if trace_references then
607                    local step = 65536
608                    result = new_hlist(colorize(width,height-step,depth-step,2,reference,"reference",texts,show_references)) -- step subtracted so that we can see seperate links
609                    current = result
610                elseif texts then
611                    texts = justadd("reference",texts,show_references,current)
612                    if texts then
613                        current = texts
614                    end
615                end
616                if current then
617                    setlink(current,annot)
618                else
619                    result = annot
620                end
621                references.registerpage(n)
622                result = new_hlist(result)
623                if cleanupreferences then stack[reference] = nil end
624                return result, resolved
625            elseif trace_references then
626                report_reference("unable to resolve annotation %a",reference)
627            end
628        elseif trace_references then
629            report_reference("unable to resolve attribute %a",reference)
630        end
631    end
632
633    function nodes.references.handler(head)
634        if head and topofstack > 0 then
635            return inject_areas(head,attribute,makereference,stack,done)
636        else
637            return head
638        end
639    end
640
641    function references.inject(prefix,reference,specification) -- todo: use currentreference is possible
642        local set, bug = references.identify(prefix,reference)
643        if bug or #set == 0 then
644            -- unknown ref, just don't set it and issue an error
645        else
646            set.highlight = specification.highlight
647            set.newwindow = specification.newwindow
648            set.layer     = specification.layer
649            setreference(specification.height,specification.depth,set) -- sets attribute / todo: for set[*].error
650        end
651    end
652
653    -- function references.injectinternal(internal,specification)
654    --     references.inject("","internal("..internal..")",specification)
655    --     if bug or #set == 0 then
656    --         -- unknown ref, just don't set it and issue an error
657    --     else
658    --         -- nil prefix when ""
659    --         -- check
660    --         set.highlight = specification.highlight
661    --         set.newwindow = specification.newwindow
662    --         set.layer     = specification.layer
663    --         setreference(specification.height,specification.depth,set) -- sets attribute / todo: for set[*].error
664    --     end
665    -- end
666
667    function references.injectcurrentset(h,d) -- used inside doifelse
668        local currentset = references.currentset
669        if currentset then
670            setreference(h,d,currentset) -- sets attribute / todo: for set[*].error
671        end
672    end
673
674end
675
676local nofdestinations = 0
677
678do
679
680    -- here we can use attributes.registervalue etc
681
682    local stack      = { }
683    local done       = { }
684    local attribute  = attributes.private('destination')
685    local topofstack = 0
686
687    nodes.destinations = {
688        attribute = attribute,
689        stack     = stack,
690        done      = done,
691    }
692
693    local function setdestination(n,h,d,name,view) -- n = grouplevel, name == table
694        topofstack = topofstack + 1
695     -- stack[topofstack] = { n, h, d, name, view }
696        stack[topofstack] = { n, h or false, d or false, name, view }
697        return topofstack
698    end
699
700    local function makedestination(width,height,depth,reference)
701        local sr = stack[reference]
702        if sr then
703            if trace_destinations then
704                report_destination("resolving attribute %a",reference)
705            end
706            local resolved = sr[1]
707            local ht       = sr[2]
708            local dp       = sr[3]
709            local name     = sr[4]
710            local view     = sr[5]
711            if ht then
712                if height < ht then height = ht end
713                if depth  < dp then depth  = dp end
714            end
715            local result, current, texts
716            if show_destinations then
717                if name and #name > 0 then
718                    local t = { }
719                    for i=1,#name do
720                        local s = name[i]
721                        if type(s) == "number" then
722                            local d = references.internals[s]
723                            if d then
724                                d = d.references
725                                local r = d.reference
726                                local p = d.usedprefix
727                                if r then
728                                    if p then
729                                        t[#t+1] = p .. "|" .. r
730                                    else
731                                        t[#t+1] = r
732                                    end
733                                else
734                                 -- t[#t+1] = d.internal or "?"
735                                end
736                            end
737                        else
738                            -- in fact we have a prefix:name here
739                        end
740                    end
741                    if #t > 0 then
742                        texts = concat(t," & ")
743                    end
744                end
745            end
746            if trace_destinations then
747                local step = 0
748                if width  == 0 then
749                    step = 4*65536
750                    width, height, depth = 5*step, 5*step, 0
751                end
752                local rule = new_hlist(colorize(width,height,depth,3,reference,"destination",texts,show_destinations))
753                if not result then
754                    result, current = rule, rule
755                else
756                    setlink(current,rule)
757                    current = rule
758                end
759                width, height = width - step, height - step
760            elseif texts then
761                texts = justadd("destination",texts,show_destinations,current)
762                if texts then
763                    current = texts
764                end
765            end
766            nofdestinations = nofdestinations + 1
767            local annot = injectdestination(width,height,depth,name,view)
768            if annot then
769                annot = tonut(annot) -- obsolete soon
770                if result then
771                    setlink(current,annot)
772                else
773                    result = annot
774                end
775                current = find_node_tail(annot)
776            end
777            if result then
778                result = new_hlist(result)
779            end
780            if cleanupdestinations then stack[reference] = nil end
781            return result, resolved
782        elseif trace_destinations then
783            report_destination("unable to resolve attribute %a",reference)
784        end
785    end
786
787    function nodes.destinations.handler(head)
788        if head and topofstack > 0 then
789            return inject_areas(head,attribute,makedestination,stack,done)
790        else
791            return head
792        end
793    end
794
795    -- will move
796
797    function references.mark(reference,h,d,view)
798        return setdestination(tex.currentgrouplevel,h,d,reference,view)
799    end
800
801end
802
803implement {
804    name      = "injectreference",
805    actions   = references.inject,
806    arguments = {
807        "string",
808        "string",
809        {
810            { "highlight", "boolean" },
811            { "newwindow", "boolean" },
812            { "layer" },
813            { "height", "dimen" },
814            { "depth", "dimen" },
815            { "view" },
816        }
817    }
818}
819
820-- implement {
821--     name      = "injectinternalreference",
822--     actions   = references.injectinternal,
823--     arguments = {
824--         "integer",
825--         {
826--             { "highlight", "boolean" },
827--             { "newwindow", "boolean" },
828--             { "layer" },
829--             { "height", "dimen" },
830--             { "depth", "dimen" },
831--             { "view" },
832--         }
833--     }
834-- }
835
836implement {
837    name      = "injectcurrentreference",
838    actions   = references.injectcurrentset,
839}
840
841implement {
842    name      = "injectcurrentreferencehtdp",
843    actions   = references.injectcurrentset,
844    arguments = { "dimen", "dimen" },
845}
846
847statistics.register("interactive elements", function()
848    if nofreferences > 0 or nofdestinations > 0 then
849        return string.format("%s references, %s destinations",nofreferences,nofdestinations)
850    else
851        return nil
852    end
853end)
854