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
11
12
13
14
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
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
135
136
137
138
139
140
141
142
143
144
145
146local inject_areas do
147
148
149
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)
161 local temp
162 if stop then
163 temp = getnext(stop)
164 setnext(stop,nil)
165 end
166 local v = vpack_list(start)
167 local w, h, d = getwhd(v)
168 setlist(v)
169 flushnode(v)
170 if temp then
171 setnext(stop,temp)
172 end
173 return w, h, d
174 end
175
176
177
178 local function dimensions(parent,start,stop)
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
185 local dp = sd == 0 and pd or sd
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)
196 end
197 elseif parent then
198
199
200 if getid(parent) == vlist_code then
201
202
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
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
260
261
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
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)
279 if line then
280
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
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
322
323 local function inject(head,skip,parent,pardir,txtdir)
324 local first, last, firstdir, reference
325 local current = head
326 while current do
327 local id, subtype = getidsubtype(current)
328 if id == hlist_code or id == vlist_code then
329 local r = getattr(current,attribute)
330
331
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
340 last = current
341 elseif (done[reference] or 0) == 0 then
342 if not skip or r > skip then
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
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
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
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
411 if not skip or r > skip then
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
420
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
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)
459 if str then
460 local typesetters = nuts.typesetters
461 if typesetters then
462 local hashes = fonts.hashes
463 local infofont = fonts.infofont(true)
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
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
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
512 local rule = setcoloring(new_rule(width,height,depth),1,u_color,u_transparency)
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)
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
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
560
561 local function setreference(h,d,r)
562 topofstack = topofstack + 1
563
564
565 stack[topofstack] = { r, h or false, d or false, codeinjections.prerollreference(r,topofstack) }
566
567 texsetcount(c_lastreferenceattribute,topofstack)
568 end
569
570 function references.get(n)
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)
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
612 if ht then
613 if height < ht then height = ht end
614 if depth < dp then depth = dp end
615 end
616
617 local annot = injectreference(reference,width,height,depth,set,resolved.mesh,makedescription(resolved),resolved.forcemesh,rotated)
618 if annot then
619 annot = tonut(annot)
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
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))
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)
673 local set, bug = references.identify(prefix,reference)
674 if bug or #set == 0 then
675
676 else
677 set.highlight = specification.highlight
678 set.newwindow = specification.newwindow
679 set.layer = specification.layer
680 setreference(specification.height,specification.depth,set)
681 end
682 end
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698 function references.injectcurrentset(h,d)
699 local currentset = references.currentset
700 if currentset then
701 setreference(h,d,currentset)
702 end
703 end
704
705end
706
707local nofdestinations = 0
708
709do
710
711
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)
725 topofstack = topofstack + 1
726
727
728
729
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
770 end
771 end
772 else
773
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
803 local annot = injectdestination(width,height,depth,name,view,internal)
804 if annot then
805 annot = tonut(annot)
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
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
857
858
859
860
861
862
863
864
865
866
867
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 |