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
16
17
18
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
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
118
119
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)
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)
139 flushnode(v)
140 if temp then
141 setnext(stop,temp)
142 end
143 return w, h, d
144end
145
146
147
148local function dimensions(parent,start,stop)
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
155 local dp = sd == 0 and pd or sd
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)
166 end
167 end
168 local last = stop and getnext(stop)
169 if parent then
170
171
172 if getid(parent) == vlist_code then
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 local first = nil
204 local last = nil
205 local current = start
206 local noflines = 0
207 while current do
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
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
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
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
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
320
321
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
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))
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
342 end
343 else
344
345 end
346 correction = width
347 else
348 correction = height + depth
349 height, depth = depth, height
350 end
351 if pardir == righttoleft_code then
352 width = - width
353 end
354 local result, resolved = make(width,height,depth,reference)
355
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
364
365 setlink(first,result,getnext(first))
366 else
367
368 setlink(result,first)
369 setlist(current,result)
370 end
371 end
372end
373
374
375
376
377
378
379
380
381
382
383
384
385local function inject_areas(head,attribute,make,stack,done,skip,parent,pardir,txtdir)
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
393
394 if r then
395 if not reference then
396 reference, first, last, firstdir = r, current, current, txtdir
397 elseif r == reference then
398
399 last = current
400 elseif (done[reference] or 0) == 0 then
401 if not skip or r > skip then
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
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
429
430 else
431 if id == glue_code then
432 local subtype = getsubtype(current)
433
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
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
452 if not skip or r > skip then
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
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)
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
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)
531 setattr(rule,a_colormodel,1)
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)
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
563
564local texsetcount = tex.setcount
565
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
580
581local function setreference(h,d,r)
582 topofstack = topofstack + 1
583
584
585 stack[topofstack] = { r, h or false, d or false, codeinjections.prerollreference(r) }
586
587 texsetcount("lastreferenceattribute",topofstack)
588end
589
590function references.get(n)
591 local sn = stack[n]
592 return sn and sn[1]
593end
594
595local function makereference(width,height,depth,reference)
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
607 if ht then
608 if height < ht then height = ht end
609 if depth < dp then depth = dp end
610 end
611
612 local annot = nodeinjections.reference(width,height,depth,set,resolved.mesh)
613 if annot then
614 annot = tonut(annot)
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
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))
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
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)
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
723 end
724 end
725 else
726
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)
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
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)
790 local set, bug = references.identify(prefix,reference)
791 if bug or #set == 0 then
792
793 else
794 set.highlight = specification.highlight
795 set.newwindow = specification.newwindow
796 set.layer = specification.layer
797 setreference(specification.height,specification.depth,set)
798 end
799end
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815function references.injectcurrentset(h,d)
816 local currentset = references.currentset
817 if currentset then
818 setreference(h,d,currentset)
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
840
841
842
843
844
845
846
847
848
849
850
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
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 |