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