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)
40
41local report_reference = logs.reporter("backend","references")
42local report_destination = logs.reporter("backend","destinations")
43local report_area = logs.reporter("backend","areas")
44
45local texiscount = tex.iscount
46local texsetcount = tex.setcount
47
48
49local injectreference = backends.nodeinjections.reference
50local injectdestination = backends.nodeinjections.destination
51local prerollreference = backends.codeinjections.prerollreference
52
53updaters.register("backends.injections.latebindings",function()
54 injectreference = backends.nodeinjections.reference
55 injectdestination = backends.nodeinjections.destination
56 prerollreference = backends.codeinjections.prerollreference
57end)
58
59local c_lastreferenceattribute = texiscount("lastreferenceattribute")
60
61local nuts = nodes.nuts
62local nodepool = nuts.pool
63
64local tonode = nuts.tonode
65local tonut = nuts.tonut
66
67local setlink = nuts.setlink
68local setnext = nuts.setnext
69local setprev = nuts.setprev
70local getnext = nuts.getnext
71local getprev = nuts.getprev
72local getid = nuts.getid
73local getlist = nuts.getlist
74local setlist = nuts.setlist
75local getwidth = nuts.getwidth
76local setwidth = nuts.setwidth
77local getheight = nuts.getheight
78local getattr = nuts.getattr
79local setattr = nuts.setattr
80local getsubtype = nuts.getsubtype
81local getwhd = nuts.getwhd
82local getdirection = nuts.getdirection
83local setshift = nuts.setshift
84local getboxglue = nuts.getboxglue
85
86local hpack_list = nuts.hpack
87local vpack_list = nuts.vpack
88local getdimensions = nuts.dimensions
89local getrangedimensions = nuts.rangedimensions
90local traverse = nuts.traverse
91local find_node_tail = nuts.tail
92
93local startofpar = nuts.startofpar
94
95local nodecodes = nodes.nodecodes
96local gluecodes = nodes.gluecodes
97local listcodes = nodes.listcodes
98
99local directioncodes = tex.directioncodes
100local lefttoright_code = directioncodes.lefttoright
101local righttoleft_code = directioncodes.righttoleft
102
103local hlist_code = nodecodes.hlist
104local vlist_code = nodecodes.vlist
105local glue_code = nodecodes.glue
106local glyph_code = nodecodes.glyph
107local rule_code = nodecodes.rule
108local dir_code = nodecodes.dir
109local par_code = nodecodes.par
110
111local leftskip_code = gluecodes.leftskip
112local lefthang_code = gluecodes.lefthangskip
113local rightskip_code = gluecodes.rightskip
114local righthang_code = gluecodes.righthangskip
115local parfillleftskip_code = gluecodes.parfillleftskip
116local parfillskip_code = gluecodes.parfillskip
117
118
119
120local new_rule = nodepool.rule
121local new_kern = nodepool.kern
122local new_hlist = nodepool.hlist
123
124local flushnode = nuts.flush
125
126local tosequence = nodes.tosequence
127
128local implement = interfaces.implement
129
130
131
132
133
134
135
136
137
138
139
140
141
142local inject_areas do
143
144
145
146
147 local function hlist_dimensions(start,stop,parent)
148 local last = stop and getnext(stop)
149 if parent then
150 return getrangedimensions(parent,start,last)
151 else
152 return getdimensions(start,last)
153 end
154 end
155
156 local function vlist_dimensions(start,stop)
157 local temp
158 if stop then
159 temp = getnext(stop)
160 setnext(stop,nil)
161 end
162 local v = vpack_list(start)
163 local w, h, d = getwhd(v)
164 setlist(v)
165 flushnode(v)
166 if temp then
167 setnext(stop,temp)
168 end
169 return w, h, d
170 end
171
172
173
174 local function dimensions(parent,start,stop)
175 if start == stop then
176 local id = getid(start)
177 if id == hlist_code or id == vlist_code or id == rule_code or id == glyph_code then
178 local sw, sh, sd = getwhd(start)
179 local pw, ph, pd = getwhd(parent)
180 local ht = sh == 0 and ph or sh
181 local dp = sd == 0 and pd or sd
182 if trace_areas then
183 report_area("dimensions taken of %a (%p,%p,%p) with parent (%p,%p,%p) -> (%p,%p,%p)",
184 nodecodes[id],sw,sh,sd,pw,ph,pd,sw,ht,dp)
185 end
186 return sw, ht, dp
187 else
188 if trace_areas then
189 report_area("dimensions calculated of %a",nodecodes[id])
190 end
191 return hlist_dimensions(start,stop)
192 end
193 elseif parent then
194
195
196 if getid(parent) == vlist_code then
197
198
199
200 local last = stop and getnext(stop)
201 local first = nil
202 local last = nil
203 local current = start
204 local noflines = 0
205 while current do
206 local id = getid(current)
207 if id == hlist_code or id == vlist_code or id == rule_code then
208 if noflines == 0 then
209 first = current
210 noflines = 1
211 else
212 noflines = noflines + 1
213 end
214 last = current
215 end
216 if current == stop then
217 break
218 else
219 current = getnext(current)
220 end
221 end
222 if noflines > 1 then
223 if trace_areas then
224 report_area("dimensions taken of vlist")
225 end
226 local w, h, d = vlist_dimensions(first,last,parent)
227 local ht = getheight(first)
228 return w, ht, d + h - ht, first
229 elseif first then
230 if trace_areas then
231 report_area("dimensions taken of first line in vlist")
232 end
233 local w, h, d = getwhd(first)
234 return w, h, d, first
235 else
236 if trace_areas then
237 report_area("dimensions taken of vlist (probably wrong)")
238 end
239 return hlist_dimensions(start,stop,parent)
240 end
241 else
242 if trace_areas then
243 report_area("dimensions taken of range starting with %a using parent",nodecodes[getid(start)])
244 end
245 return hlist_dimensions(start,stop,parent)
246 end
247 else
248 if trace_areas then
249 report_area("dimensions taken of range starting with %a",nodecodes[getid(start)])
250 end
251 return hlist_dimensions(start,stop)
252 end
253 end
254
255
256
257
258
259 local attribute, make, stack, done
260
261 local function inject_range(head,first,last,reference,parent,pardir,txtdir)
262 local width, height, depth, line = dimensions(parent,first,last)
263 if txtdir == righttoleft_code then
264 width = - width
265 elseif txtdir == lefttoright_code then
266
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
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
300 report_area("compensating for link in vlist")
301 end
302 setlink(result,getlist(first))
303 setlist(first,result)
304 else
305 setlink(getprev(first),result,first)
306 end
307 return head, last
308 end
309 else
310 return head, last
311 end
312 end
313
314
315
316
317
318
319 local function inject(head,skip,parent,pardir,txtdir)
320 local first, last, firstdir, reference
321 local current = head
322 while current do
323 local id = getid(current)
324 if id == hlist_code or id == vlist_code then
325 local r = getattr(current,attribute)
326
327
328 if r then
329 if not reference then
330 reference = r
331 first = current
332 last = current
333 firstdir = txtdir
334 elseif r == reference then
335
336 last = current
337 elseif (done[reference] or 0) == 0 then
338 if not skip or r > skip then
339 head, current = inject_range(head,first,last,reference,parent,pardir,firstdir)
340 reference = nil
341 first = nil
342 last = nil
343 firstdir = nil
344 end
345 else
346 reference = r
347 first = current
348 last = current
349 firstdir = txtdir
350 end
351 done[r] = (done[r] or 0) + 1
352 end
353 local list = getlist(current)
354 if list then
355 local h
356 h, pardir, txtdir = inject(list,r or skip or 0,current,pardir,txtdir)
357 if h ~= current then
358 setlist(current,h)
359 end
360 end
361 if r then
362 done[r] = done[r] - 1
363 end
364 goto NEXT
365 elseif id == dir_code then
366 local direction, pop = getdirection(current)
367 txtdir = not pop and direction
368 goto NEXT
369 elseif id == par_code then
370 if startofpar(current) then
371 pardir = getdirection(current)
372 end
373 goto NEXT
374 elseif id == glue_code then
375
376 local subtype = getsubtype(current)
377 if subtype == leftskip_code or subtype == lefthang_code or subtype == parfillleftskip_code then
378 goto NEXT
379 elseif subtype == rightskip_code or subtype == righthang_code or subtype == parfillskip_code then
380 if reference and (done[reference] or 0) == 0 then
381 head, current = inject_range(head,first,last,reference,parent,pardir,firstdir)
382 reference = nil
383 first = nil
384 last = nil
385 firstdir = nil
386 end
387 goto NEXT
388 end
389 end
390 do
391 local r = getattr(current,attribute)
392 if not r then
393
394 elseif not reference then
395 reference = r
396 first = current
397 last = current
398 firstdir = txtdir
399 elseif r == reference then
400 last = current
401 elseif (done[reference] or 0) == 0 then
402 if not skip or r > skip then
403 head, current = inject_range(head,first,last,reference,parent,pardir,firstdir)
404 reference = nil
405 first = nil
406 last = nil
407 firstdir = nil
408 else
409
410 end
411 else
412
413
414 reference = r
415 first = current
416 last = current
417 firstdir = txtdir
418 end
419 end
420 ::NEXT::
421 current = getnext(current)
422 end
423 if reference and (done[reference] or 0) == 0 then
424 head = inject_range(head,first,last,reference,parent,pardir,firstdir)
425 end
426 return head, pardir, txtdir
427 end
428
429 inject_areas = function(head,a,m,s,d)
430 attribute = a
431 make = m
432 stack = s
433 done = d
434 return (inject(head))
435 end
436
437end
438
439
440
441local colorize, justadd do
442
443 local u_transparency = nil
444 local u_colors = { }
445 local force_gray = true
446
447 local register_color = colors.register
448
449 local setcoloring = nuts.colors.set
450
451 local function addstring(what,str,shift)
452 if str then
453 local typesetters = nuts.typesetters
454 if typesetters then
455 local hashes = fonts.hashes
456 local infofont = fonts.infofont(true)
457 local emwidth = hashes.emwidths [infofont]
458 local exheight = hashes.exheights[infofont]
459 if what == "reference" then
460 str = str .. " "
461 shift = - (shift or 2.25) * exheight
462 else
463 str = str .. " "
464 shift = (shift or 2) * exheight
465 end
466
467 local text = typesetters.tohpack(str,infofont)
468 local rule = new_rule(emwidth/5,4*exheight,3*exheight)
469 setshift(text,shift)
470 return hpack_list(setlink(text,rule))
471 end
472 end
473 end
474
475 colorize = function(width,height,depth,n,reference,what,sr,offset)
476 if force_gray then
477 n = 0
478 end
479 u_transparency = u_transparency or transparencies.register(nil,2,.65)
480 local ucolor = u_colors[n]
481 if not ucolor then
482 if n == 1 then
483 u_color = register_color(nil,'rgb',.75,0,0)
484 elseif n == 2 then
485 u_color = register_color(nil,'rgb',0,.75,0)
486 elseif n == 3 then
487 u_color = register_color(nil,'rgb',0,0,.75)
488 else
489 n = 0
490 u_color = register_color(nil,'gray',.5)
491 end
492 u_colors[n] = u_color
493 end
494 if width == 0 then
495
496 report_area("%s %s has no %s dimensions, width %p, height %p, depth %p",what,reference,"horizontal",width,height,depth)
497 width = 65536
498 end
499 if height + depth <= 0 then
500 report_area("%s %s has no %s dimensions, width %p, height %p, depth %p",what,reference,"vertical",width,height,depth)
501 height = 65536/2
502 depth = height
503 end
504
505 local rule = setcoloring(new_rule(width,height,depth),1,u_color,u_transparency)
506 if width < 0 then
507 local kern = new_kern(width)
508 setwidth(rule,-width)
509 setlink(kern,rule)
510 return kern
511 elseif sr and sr ~= "" then
512 local text = addstring(what,sr,shift)
513 if text then
514 local kern = new_kern(-getwidth(text))
515 setlink(kern,text,rule)
516 return kern
517 end
518 end
519 return rule
520 end
521
522 justadd = function(what,sr,shift,current)
523 if sr and sr ~= "" then
524 local text = addstring(what,sr,shift)
525 if text then
526 local kern = new_kern(-getwidth(text))
527 setlink(kern,text,current)
528 return new_hlist(kern)
529 end
530 end
531 end
532
533end
534
535local nofreferences = 0
536
537do
538
539
540
541 local stack = { }
542 local done = { }
543 local attribute = attributes.private('reference')
544 local topofstack = 0
545
546 nodes.references = {
547 attribute = attribute,
548 stack = stack,
549 done = done,
550 }
551
552
553
554 local function setreference(h,d,r)
555 topofstack = topofstack + 1
556
557
558 stack[topofstack] = { r, h or false, d or false, codeinjections.prerollreference(r) }
559
560 texsetcount(c_lastreferenceattribute,topofstack)
561 end
562
563 function references.get(n)
564 local sn = stack[n]
565 return sn and sn[1]
566 end
567
568 local function makereference(width,height,depth,reference)
569 local sr = stack[reference]
570 if sr then
571 if trace_references then
572 report_reference("resolving attribute %a",reference)
573 end
574 local resolved = sr[1]
575 local ht = sr[2]
576 local dp = sr[3]
577 local set = sr[4]
578 local n = sr[5]
579
580 if ht then
581 if height < ht then height = ht end
582 if depth < dp then depth = dp end
583 end
584
585 local annot = injectreference(reference,width,height,depth,set,resolved.mesh)
586 if annot then
587 annot = tonut(annot)
588 nofreferences = nofreferences + 1
589 local result, current, texts
590 if show_references then
591 local d = resolved
592 if d then
593 local r = d.reference
594 local p = d.prefix
595 if r then
596 if p then
597 texts = p .. "|" .. r
598 else
599 texts = r
600 end
601 else
602
603 end
604 end
605 end
606 if trace_references then
607 local step = 65536
608 result = new_hlist(colorize(width,height-step,depth-step,2,reference,"reference",texts,show_references))
609 current = result
610 elseif texts then
611 texts = justadd("reference",texts,show_references,current)
612 if texts then
613 current = texts
614 end
615 end
616 if current then
617 setlink(current,annot)
618 else
619 result = annot
620 end
621 references.registerpage(n)
622 result = new_hlist(result)
623 if cleanupreferences then stack[reference] = nil end
624 return result, resolved
625 elseif trace_references then
626 report_reference("unable to resolve annotation %a",reference)
627 end
628 elseif trace_references then
629 report_reference("unable to resolve attribute %a",reference)
630 end
631 end
632
633 function nodes.references.handler(head)
634 if head and topofstack > 0 then
635 return inject_areas(head,attribute,makereference,stack,done)
636 else
637 return head
638 end
639 end
640
641 function references.inject(prefix,reference,specification)
642 local set, bug = references.identify(prefix,reference)
643 if bug or #set == 0 then
644
645 else
646 set.highlight = specification.highlight
647 set.newwindow = specification.newwindow
648 set.layer = specification.layer
649 setreference(specification.height,specification.depth,set)
650 end
651 end
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667 function references.injectcurrentset(h,d)
668 local currentset = references.currentset
669 if currentset then
670 setreference(h,d,currentset)
671 end
672 end
673
674end
675
676local nofdestinations = 0
677
678do
679
680
681
682 local stack = { }
683 local done = { }
684 local attribute = attributes.private('destination')
685 local topofstack = 0
686
687 nodes.destinations = {
688 attribute = attribute,
689 stack = stack,
690 done = done,
691 }
692
693 local function setdestination(n,h,d,name,view)
694 topofstack = topofstack + 1
695
696 stack[topofstack] = { n, h or false, d or false, name, view }
697 return topofstack
698 end
699
700 local function makedestination(width,height,depth,reference)
701 local sr = stack[reference]
702 if sr then
703 if trace_destinations then
704 report_destination("resolving attribute %a",reference)
705 end
706 local resolved = sr[1]
707 local ht = sr[2]
708 local dp = sr[3]
709 local name = sr[4]
710 local view = sr[5]
711 if ht then
712 if height < ht then height = ht end
713 if depth < dp then depth = dp end
714 end
715 local result, current, texts
716 if show_destinations then
717 if name and #name > 0 then
718 local t = { }
719 for i=1,#name do
720 local s = name[i]
721 if type(s) == "number" then
722 local d = references.internals[s]
723 if d then
724 d = d.references
725 local r = d.reference
726 local p = d.usedprefix
727 if r then
728 if p then
729 t[#t+1] = p .. "|" .. r
730 else
731 t[#t+1] = r
732 end
733 else
734
735 end
736 end
737 else
738
739 end
740 end
741 if #t > 0 then
742 texts = concat(t," & ")
743 end
744 end
745 end
746 if trace_destinations then
747 local step = 0
748 if width == 0 then
749 step = 4*65536
750 width, height, depth = 5*step, 5*step, 0
751 end
752 local rule = new_hlist(colorize(width,height,depth,3,reference,"destination",texts,show_destinations))
753 if not result then
754 result, current = rule, rule
755 else
756 setlink(current,rule)
757 current = rule
758 end
759 width, height = width - step, height - step
760 elseif texts then
761 texts = justadd("destination",texts,show_destinations,current)
762 if texts then
763 current = texts
764 end
765 end
766 nofdestinations = nofdestinations + 1
767 local annot = injectdestination(width,height,depth,name,view)
768 if annot then
769 annot = tonut(annot)
770 if result then
771 setlink(current,annot)
772 else
773 result = annot
774 end
775 current = find_node_tail(annot)
776 end
777 if result then
778 result = new_hlist(result)
779 end
780 if cleanupdestinations then stack[reference] = nil end
781 return result, resolved
782 elseif trace_destinations then
783 report_destination("unable to resolve attribute %a",reference)
784 end
785 end
786
787 function nodes.destinations.handler(head)
788 if head and topofstack > 0 then
789 return inject_areas(head,attribute,makedestination,stack,done)
790 else
791 return head
792 end
793 end
794
795
796
797 function references.mark(reference,h,d,view)
798 return setdestination(tex.currentgrouplevel,h,d,reference,view)
799 end
800
801end
802
803implement {
804 name = "injectreference",
805 actions = references.inject,
806 arguments = {
807 "string",
808 "string",
809 {
810 { "highlight", "boolean" },
811 { "newwindow", "boolean" },
812 { "layer" },
813 { "height", "dimen" },
814 { "depth", "dimen" },
815 { "view" },
816 }
817 }
818}
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836implement {
837 name = "injectcurrentreference",
838 actions = references.injectcurrentset,
839}
840
841implement {
842 name = "injectcurrentreferencehtdp",
843 actions = references.injectcurrentset,
844 arguments = { "dimen", "dimen" },
845}
846
847statistics.register("interactive elements", function()
848 if nofreferences > 0 or nofdestinations > 0 then
849 return string.format("%s references, %s destinations",nofreferences,nofdestinations)
850 else
851 return nil
852 end
853end)
854 |