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