1if not modules then modules = { } end modules ['trac-vis'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to trac-vis.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
10local node, nodes, attributes, tex = node, nodes, attributes, tex
11local type, tonumber, next, rawget = type, tonumber, next, rawget
12local gmatch = string.gmatch
13local formatters = string.formatters
14local round = math.round
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33local nodecodes = nodes.nodecodes
34
35local nuts = nodes.nuts
36local tonut = nuts.tonut
37
38local setboth = nuts.setboth
39local setlink = nuts.setlink
40local setdisc = nuts.setdisc
41local setlist = nuts.setlist
42local setleader = nuts.setleader
43local setsubtype = nuts.setsubtype
44local setattr = nuts.setattr
45local setwidth = nuts.setwidth
46local setshift = nuts.setshift
47
48local getid = nuts.getid
49local getfont = nuts.getfont
50local getattr = nuts.getattr
51local getsubtype = nuts.getsubtype
52local getbox = nuts.getbox
53local getlist = nuts.getlist
54local getleader = nuts.getleader
55local getnext = nuts.getnext
56local getprev = nuts.getprev
57local getboth = nuts.getboth
58local getdisc = nuts.getdisc
59local getwhd = nuts.getwhd
60local getkern = nuts.getkern
61local getpenalty = nuts.getpenalty
62local getwidth = nuts.getwidth
63local getdepth = nuts.getdepth
64local getshift = nuts.getshift
65local getexpansion = nuts.getexpansion
66local getdirection = nuts.getdirection
67local getstate = nuts.getstate
68
69local isglyph = nuts.isglyph
70
71local hpack_nodes = nuts.hpack
72local vpack_nodes = nuts.vpack
73local copylist = nuts.copylist
74local copy_node = nuts.copy
75local insertnodebefore = nuts.insertbefore
76local insertnodeafter = nuts.insertafter
77local apply_to_nodes = nuts.apply
78local find_tail = nuts.tail
79local effectiveglue = nuts.effectiveglue
80local flushnodelist = nuts.flushlist
81
82local hpack_string = nuts.typesetters.tohpack
83
84local texgetattribute = tex.getattribute
85local texsetattribute = tex.setattribute
86
87local setmetatableindex = table.setmetatableindex
88
89local unsetvalue = attributes.unsetvalue
90
91local current_font = font.current
92
93local fonthashes = fonts.hashes
94local chardata = fonthashes.characters
95local exheights = fonthashes.exheights
96local emwidths = fonthashes.emwidths
97local pt_factor = number.dimenfactors.pt
98
99local nodepool = nuts.pool
100local new_rule = nodepool.rule
101local new_kern = nodepool.kern
102local new_glue = nodepool.glue
103local new_hlist = nodepool.hlist
104local new_vlist = nodepool.vlist
105
106local tracers = nodes.tracers
107local visualizers = nodes.visualizers
108
109local setcolor = tracers.colors.set
110local setlistcolor = tracers.colors.setlist
111local settransparency = tracers.transparencies.set
112local setlisttransparency = tracers.transparencies.setlist
113
114local starttiming = statistics.starttiming
115local stoptiming = statistics.stoptiming
116
117local a_visual = attributes.private("visual")
118local a_layer = attributes.private("viewerlayer")
119
120local band = bit32.band
121local bor = bit32.bor
122
123local enableaction = nodes.tasks.enableaction
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142local report_visualize = logs.reporter("visualize")
143
144local modes = {
145 hbox = 0x000001,
146 vbox = 0x000002,
147 vtop = 0x000004,
148 kern = 0x000008,
149 glue = 0x000010,
150 penalty = 0x000020,
151 fontkern = 0x000040,
152 strut = 0x000080,
153 whatsit = 0x000100,
154 glyph = 0x000200,
155 simple = 0x000400,
156 simplehbox = 0x000401,
157 simplevbox = 0x000402,
158 simplevtop = 0x000404,
159 user = 0x000800,
160 math = 0x001000,
161 italic = 0x002000,
162 origin = 0x004000,
163 discretionary = 0x008000,
164 expansion = 0x010000,
165 line = 0x020000,
166 space = 0x040000,
167 depth = 0x080000,
168 marginkern = 0x100000,
169 mathlistkern = 0x200000,
170 dir = 0x400000,
171 par = 0x800000,
172}
173
174local usedfont, exheight, emwidth
175local l_penalty, l_glue, l_kern, l_fontkern, l_hbox, l_vbox, l_vtop, l_strut, l_whatsit, l_glyph, l_user, l_math, l_marginkern, l_mathlistkern, l_italic, l_origin, l_discretionary, l_expansion, l_line, l_space, l_depth,
176 l_dir, l_whatsit
177
178local enabled = false
179local layers = { }
180
181local preset_boxes = modes.hbox + modes.vbox + modes.origin
182local preset_makeup = preset_boxes
183 + modes.kern + modes.glue + modes.penalty
184local preset_all = preset_makeup
185 + modes.fontkern + modes.marginkern + modes.mathlistkern
186 + modes.whatsit + modes.glyph + modes.user + modes.math
187 + modes.dir + modes.whatsit
188
189function visualizers.setfont(id)
190 usedfont = id or current_font()
191 exheight = exheights[usedfont]
192 emwidth = emwidths[usedfont]
193end
194
195
196
197local userrule
198local outlinerule
199
200local function initialize()
201
202 if not usedfont then
203
204 visualizers.setfont(fonts.definers.define { name = "lmmonoltcond10regular", size = tex.sp("4pt") })
205 end
206
207 for mode, value in next, modes do
208 local tag = formatters["v_%s"](mode)
209 attributes.viewerlayers.define {
210 tag = tag,
211 title = formatters["visualizer %s"](mode),
212 visible = "start",
213 editable = "yes",
214 printable = "yes"
215 }
216 layers[mode] = attributes.viewerlayers.register(tag,true)
217 end
218 l_hbox = layers.hbox
219 l_vbox = layers.vbox
220 l_vtop = layers.vtop
221 l_glue = layers.glue
222 l_kern = layers.kern
223 l_penalty = layers.penalty
224 l_fontkern = layers.fontkern
225 l_strut = layers.strut
226 l_whatsit = layers.whatsit
227 l_glyph = layers.glyph
228 l_user = layers.user
229 l_math = layers.math
230 l_italic = layers.italic
231 l_marginkern = layers.marginkern
232 l_mathlistkern = layers.mathlistkern
233 l_origin = layers.origin
234 l_discretionary = layers.discretionary
235 l_expansion = layers.expansion
236 l_line = layers.line
237 l_space = layers.space
238 l_depth = layers.depth
239 l_dir = layers.dir
240 l_par = layers.par
241
242 if not userrule then
243 userrule = nuts.rules.userrule
244 end
245
246 if not outlinerule then
247 outlinerule = nuts.pool.outlinerule
248 end
249 initialize = false
250end
251
252local function enable()
253 if initialize then
254 initialize()
255 end
256 enableaction("shipouts","nodes.visualizers.handler")
257 report_visualize("enabled")
258 enabled = true
259 tex.setcount("global","c_syst_visualizers_state",1)
260end
261
262local function setvisual(n,a,what,list)
263 if not n or n == "reset" then
264 return unsetvalue
265 elseif n == true or n == "makeup" then
266 if not a or a == 0 or a == unsetvalue then
267 a = preset_makeup
268 else
269 a = bor(a,preset_makeup)
270 end
271 elseif n == "boxes" then
272 if not a or a == 0 or a == unsetvalue then
273 a = preset_boxes
274 else
275 a = bor(a,preset_boxes)
276 end
277 elseif n == "all" then
278 if what == false then
279 return unsetvalue
280 elseif not a or a == 0 or a == unsetvalue then
281 a = preset_all
282 else
283 a = bor(a,preset_all)
284 end
285 else
286 for s in gmatch(n,"[a-z]+") do
287 local m = modes[s]
288 if not m then
289
290 elseif not a or a == 0 or a == unsetvalue then
291 a = m
292 else
293 a = bor(a,m)
294 end
295 end
296 end
297 if not a or a == 0 or a == unsetvalue then
298 return unsetvalue
299 elseif not enabled then
300 enable()
301 end
302 return a
303end
304
305function nuts.setvisual(n,mode)
306 setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true))
307end
308
309function nuts.setvisuals(n,mode)
310 setattr(n,a_visual,setvisual(mode,getattr(n,a_visual),true,true))
311end
312
313
314
315do
316
317 local cached = setmetatableindex(function(t,k)
318 if k == true then
319 return texgetattribute(a_visual)
320 elseif not k then
321 t[k] = unsetvalue
322 return unsetvalue
323 else
324 local v = setvisual(k)
325 t[k] = v
326 return v
327 end
328 end)
329
330
331
332
333
334
335 local a = unsetvalue
336
337 local f = function(n) setattr(n,a_visual,a) end
338
339 local function applyvisuals(n,mode)
340 a = cached[mode]
341 apply_to_nodes(n,f)
342 end
343
344 nuts.applyvisuals = applyvisuals
345
346 function nodes.applyvisuals(n,mode)
347 applyvisuals(tonut(n),mode)
348 end
349
350 function visualizers.attribute(mode)
351 return cached[mode]
352 end
353
354 visualizers.attributes = cached
355
356end
357
358function nuts.copyvisual(n,m)
359 setattr(n,a_visual,getattr(m,a_visual))
360end
361
362function visualizers.setvisual(n)
363 texsetattribute(a_visual,setvisual(n,texgetattribute(a_visual)))
364end
365
366function visualizers.setlayer(n)
367 texsetattribute(a_layer,layers[n] or unsetvalue)
368end
369
370local function set(mode,v)
371 texsetattribute(a_visual,setvisual(mode,texgetattribute(a_visual),v))
372end
373
374for mode, value in next, modes do
375 trackers.register(formatters["visualizers.%s"](mode), function(v) set(mode,v) end)
376end
377
378local fraction = 10
379
380trackers .register("visualizers.reset", function(v) set("reset", v) end)
381trackers .register("visualizers.all", function(v) set("all", v) end)
382trackers .register("visualizers.makeup", function(v) set("makeup",v) end)
383trackers .register("visualizers.boxes", function(v) set("boxes", v) end)
384directives.register("visualizers.fraction", function(v) fraction = (v and tonumber(v)) or (v == "more" and 5) or 10 end)
385
386local c_positive = "trace:b"
387local c_negative = "trace:r"
388local c_zero = "trace:g"
389local c_text = "trace:s"
390local c_space = "trace:y"
391local c_space_x = "trace:m"
392local c_skip_a = "trace:c"
393local c_skip_b = "trace:m"
394local c_glyph = "trace:o"
395local c_ligature = "trace:s"
396local c_white = "trace:w"
397
398
399
400
401local c_depth = "trace:o"
402local c_indent = "trace:s"
403
404local c_positive_d = "trace:db"
405local c_negative_d = "trace:dr"
406local c_zero_d = "trace:dg"
407local c_text_d = "trace:ds"
408local c_space_d = "trace:dy"
409local c_space_x_d = "trace:dm"
410local c_skip_a_d = "trace:dc"
411local c_skip_b_d = "trace:dm"
412local c_glyph_d = "trace:do"
413local c_ligature_d = "trace:ds"
414local c_white_d = "trace:dw"
415local c_math_d = "trace:dr"
416local c_origin_d = "trace:do"
417local c_discretionary_d = "trace:dd"
418
419
420
421
422local function sometext(str,layer,color,textcolor,lap)
423 local text = hpack_string(str,usedfont)
424 local size = getwidth(text)
425 local rule = new_rule(size,2*exheight,exheight/2)
426 local kern = new_kern(-size)
427 if color then
428 setcolor(rule,color)
429 end
430 if textcolor then
431 setlistcolor(getlist(text),textcolor)
432 end
433 local info = setlink(rule,kern,text)
434 setlisttransparency(info,c_zero)
435 info = hpack_nodes(info)
436 local width = getwidth(info)
437 if lap then
438 info = new_hlist(setlink(new_kern(-width),info))
439 else
440 info = new_hlist(info)
441 end
442 if layer then
443 setattr(info,a_layer,layer)
444 end
445 return info, width
446end
447
448local function someblob(str,layer,color,textcolor,width)
449 local text = hpack_string(str,usedfont)
450 local size = getwidth(text)
451 local rule = new_rule(width,2*exheight,exheight/2)
452 local kern = new_kern(-width + (width-size)/2)
453 if color then
454 setcolor(rule,color)
455 end
456 if textcolor then
457 setlistcolor(getlist(text),textcolor)
458 end
459 local info = setlink(rule,kern,text)
460 setlisttransparency(info,c_zero)
461 info = hpack_nodes(info)
462 local width = getwidth(info)
463 info = new_hlist(info)
464 if layer then
465 setattr(info,a_layer,layer)
466 end
467 return info, width
468end
469
470local caches = setmetatableindex("table")
471
472local fontkern, italickern, marginkern, mathlistkern do
473
474 local f_cache = caches["fontkern"]
475 local i_cache = caches["italickern"]
476 local m_cache = caches["marginkern"]
477 local l_cache = caches["mathlistkern"]
478
479 local function somekern(head,current,cache,color,layer)
480 local width = getkern(current)
481 local extra = getexpansion(current)
482 local kern = width + extra
483 local info = cache[kern]
484 if not info then
485 local text = hpack_string(formatters[" %0.3f"](kern*pt_factor),usedfont)
486 local rule = new_rule(emwidth/fraction,6*exheight,2*exheight)
487 local list = getlist(text)
488 if kern > 0 then
489 setlistcolor(list,c_positive_d)
490 elseif kern < 0 then
491 setlistcolor(list,c_negative_d)
492 else
493 setlistcolor(list,c_zero_d)
494 end
495 setlisttransparency(list,color)
496 setcolor(rule,color)
497 settransparency(rule,color)
498 setshift(text,-5 * exheight)
499 info = new_hlist(setlink(rule,text))
500 setattr(info,a_layer,layer)
501 f_cache[kern] = info
502 end
503 head = insertnodebefore(head,current,copylist(info))
504 return head, current
505 end
506
507 fontkern = function(head,current)
508 return somekern(head,current,f_cache,c_text_d,l_fontkern)
509 end
510
511 italickern = function(head,current)
512 return somekern(head,current,i_cache,c_glyph_d,l_italic)
513 end
514
515 marginkern = function(head,current)
516 return somekern(head,current,m_cache,c_glyph_d,l_marginkern)
517 end
518
519 mathlistkern = function(head,current)
520 return somekern(head,current,l_cache,c_glyph_d,l_mathlistkern)
521 end
522
523end
524
525local glyphexpansion do
526
527 local f_cache = caches["glyphexpansion"]
528
529 glyphexpansion = function(head,current)
530 local extra = getexpansion(current)
531 if extra and extra ~= 0 then
532 extra = extra / 1000
533 local info = f_cache[extra]
534 if not info then
535 local text = hpack_string(round(extra),usedfont)
536 local rule = new_rule(emwidth/fraction,exheight,2*exheight)
537 local list = getlist(text)
538 if extra > 0 then
539 setlistcolor(list,c_positive_d)
540 elseif extra < 0 then
541 setlistcolor(list,c_negative_d)
542 end
543 setlisttransparency(list,c_text_d)
544 setcolor(rule,c_text_d)
545 settransparency(rule,c_text_d)
546 setshift(text,1.5 * exheight)
547 info = new_hlist(setlink(rule,text))
548 setattr(info,a_layer,l_expansion)
549 f_cache[extra] = info
550 end
551 head = insertnodebefore(head,current,copylist(info))
552 return head, current
553 end
554 return head, current
555 end
556
557end
558
559local kernexpansion do
560
561 local f_cache = caches["kernexpansion"]
562
563
564
565 kernexpansion = function(head,current)
566 local extra = getexpansion(current)
567 if extra ~= 0 then
568 extra = extra / 1000
569 local info = f_cache[extra]
570 if not info then
571 local text = hpack_string(round(extra),usedfont)
572 local rule = new_rule(emwidth/fraction,exheight,4*exheight)
573 local list = getlist(text)
574 if extra > 0 then
575 setlistcolor(list,c_positive_d)
576 elseif extra < 0 then
577 setlistcolor(list,c_negative_d)
578 end
579 setlisttransparency(list,c_text_d)
580 setcolor(rule,c_text_d)
581 settransparency(rule,c_text_d)
582 setshift(text,3.5 * exheight)
583 info = new_hlist(setlink(rule,text))
584 setattr(info,a_layer,l_expansion)
585 f_cache[extra] = info
586 end
587 head = insertnodebefore(head,current,copylist(info))
588 return head, current
589 end
590 return head, current
591 end
592
593end
594
595local whatsit do
596
597 local whatsitcodes = nodes.whatsitcodes
598 local w_cache = caches["whatsit"]
599
600 local tags = {
601 open = "OPN",
602 write = "WRI",
603 close = "CLS",
604 special = "SPE",
605 latelua = "LUA",
606 savepos = "POS",
607 userdefined = "USR",
608 literal = "LIT",
609 setmatrix = "MAT",
610 save = "SAV",
611 restore = "RES",
612 }
613
614 whatsit = function(head,current)
615 local what = getsubtype(current)
616 local info = w_cache[what]
617 if info then
618
619 else
620 info = sometext(formatters["W:%s"](what),usedfont,nil,c_white)
621 setattr(info,a_layer,l_whatsit)
622 w_cache[what] = info
623 end
624 head, current = insertnodeafter(head,current,copylist(info))
625 return head, current
626 end
627
628end
629
630local dir, par do
631
632 local dircodes = nodes.dircodes
633 local dirvalues = nodes.dirvalues
634
635 local cancel_code = dircodes.cancel
636 local l2r_code = dirvalues.l2r
637 local r2l_code = dirvalues.r2l
638
639 local d_cache = caches["dir"]
640
641 local tags = {
642 l2r = "L2R",
643 r2l = "R2L",
644 cancel = "CAN",
645 par = "PAR",
646 }
647
648 par = function(head,current)
649 local what = "par"
650 local info = d_cache[what]
651 if info then
652
653 else
654 info = sometext(formatters["L:%s"](what),usedfont,nil,c_white)
655 setattr(info,a_layer,l_dir)
656 d_cache[what] = info
657 end
658 return head, current
659 end
660
661 dir = function(head,current)
662 local what = getsubtype(current)
663 if what == cancelcode then
664 what = "cancel"
665 elseif getdirection(current) == r2l_code then
666 what = "r2l"
667 else
668 what = "l2r"
669 end
670 local info = d_cache[what]
671 if info then
672
673 else
674 info = sometext(formatters["D:%s"](what),usedfont,nil,c_white)
675 setattr(info,a_layer,l_dir)
676 d_cache[what] = info
677 end
678 return head, current
679 end
680
681end
682
683local user do
684
685 local u_cache = caches["user"]
686
687 user = function(head,current)
688 local what = getsubtype(current)
689 local info = u_cache[what]
690 if info then
691
692 else
693 info = sometext(formatters["U:%s"](what),usedfont)
694 setattr(info,a_layer,l_user)
695 u_cache[what] = info
696 end
697 head, current = insertnodeafter(head,current,copylist(info))
698 return head, current
699 end
700
701end
702
703local math do
704
705 local mathcodes = nodes.mathcodes
706 local m_cache = {
707 beginmath = caches["bmath"],
708 endmath = caches["emath"],
709 }
710 local tags = {
711 beginmath = "B",
712 endmath = "E",
713 }
714
715 math = function(head,current)
716 local what = getsubtype(current)
717 local tag = mathcodes[what]
718 local skip = getkern(current) + getwidth(current)
719 local info = m_cache[tag][skip]
720 if info then
721
722 else
723 local text, width = sometext(formatters["M:%s"](tag and tags[tag] or what),usedfont,nil,c_math_d)
724 local rule = new_rule(skip,-655360/fraction,2*655360/fraction)
725 setcolor(rule,c_math_d)
726 settransparency(rule,c_math_d)
727 setattr(rule,a_layer,l_math)
728 if tag == "beginmath" then
729 info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-width),text))
730 else
731 info = new_hlist(setlink(new_glue(-skip),rule,new_glue(-skip),text))
732 end
733 setattr(info,a_layer,l_math)
734 m_cache[tag][skip] = info
735 end
736 head, current = insertnodeafter(head,current,copylist(info))
737 return head, current
738 end
739
740end
741
742local ruleddepth do
743
744 ruleddepth = function(current,wd,ht,dp)
745 local wd, ht, dp = getwhd(current)
746 if dp ~= 0 then
747 local rule = new_rule(wd,0,dp)
748 setcolor(rule,c_depth)
749 settransparency(rule,c_zero)
750 setattr(rule,a_layer,l_depth)
751 setlist(current,setlink(rule,new_kern(-wd),getlist(current)))
752 end
753 end
754
755end
756
757local ruledbox do
758
759 local b_cache = caches["box"]
760 local o_cache = caches["origin"]
761
762 setmetatableindex(o_cache,function(t,size)
763 local rule = new_rule(2*size,size,size)
764 local origin = hpack_nodes(rule)
765 setcolor(rule,c_origin_d)
766 settransparency(rule,c_origin_d)
767 setattr(rule,a_layer,l_origin)
768 t[size] = origin
769 return origin
770 end)
771
772 ruledbox = function(head,current,vertical,layer,what,simple,previous,trace_origin,parent)
773 local wd, ht, dp = getwhd(current)
774 if wd ~= 0 then
775 local shift = getshift(current)
776 local next = getnext(current)
777 local prev = previous
778 setboth(current)
779 local linewidth = emwidth/fraction
780 local size = 2*linewidth
781 local this
782 if not simple then
783 this = b_cache[what]
784 if not this then
785 local text = hpack_string(what,usedfont)
786 this = setlink(new_kern(-getwidth(text)),text)
787 setlisttransparency(this,c_text)
788 this = new_hlist(this)
789 b_cache[what] = this
790 end
791 end
792
793 local info = setlink(
794 this and copylist(this) or nil,
795 (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule {
796 width = wd,
797 height = ht,
798 depth = dp,
799 line = linewidth,
800 type = "box",
801 dashed = 3*size,
802 }
803 )
804
805 setlisttransparency(info,c_text)
806 info = new_hlist(info)
807
808 setattr(info,a_layer,layer)
809 if vertical then
810 if shift == 0 then
811 info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
812 elseif trace_origin then
813 local size = 2*size
814 local origin = o_cache[size]
815 origin = copylist(origin)
816 if getid(parent) == vlist_code then
817 setshift(origin,-shift)
818 info = setlink(current,new_kern(-size),origin,new_kern(-size-dp),info)
819 else
820
821 info = setlink(current,dp ~= 0 and new_kern(-dp) or nil,info)
822 end
823 setshift(current,0)
824 else
825 info = setlink(current,new_dp ~= 0 and new_kern(-dp) or nil,info)
826 setshift(current,0)
827 end
828 info = new_vlist(info,wd,ht,dp,shift)
829 else
830 if shift == 0 then
831 info = setlink(current,new_kern(-wd),info)
832 elseif trace_origin then
833 local size = 2*size
834 local origin = o_cache[size]
835 origin = copylist(origin)
836 if getid(parent) == vlist_code then
837 info = setlink(current,new_kern(-wd-size-shift),origin,new_kern(-size+shift),info)
838 else
839 setshift(origin,-shift)
840 info = setlink(current,new_kern(-wd-size),origin,new_kern(-size),info)
841 end
842 setshift(current,0)
843 else
844 info = setlink(current,new_kern(-wd),info)
845 setshift(current,0)
846 end
847 info = new_hlist(info,wd,ht,dp,shift)
848 end
849 if next then
850 setlink(info,next)
851 end
852 if prev and prev > 0 then
853 setlink(prev,info)
854 end
855 if head == current then
856 return info, info
857 else
858 return head, info
859 end
860 else
861 return head, current
862 end
863 end
864
865end
866
867local ruledglyph do
868
869
870
871
872
873 ruledglyph = function(head,current,previous)
874 local wd = getwidth(current)
875 if wd ~= 0 then
876 local wd, ht, dp = getwhd(current)
877 local next = getnext(current)
878 local prev = previous
879 setboth(current)
880 local linewidth = emwidth/(2*fraction)
881 local info
882
883 info = setlink(
884 (dp == 0 and outlinerule and outlinerule(wd,ht,dp,linewidth)) or userrule {
885 width = wd,
886 height = ht,
887 depth = dp,
888 line = linewidth,
889 type = "box",
890 },
891 new_kern(-wd)
892 )
893
894 local c, f = isglyph(current)
895 local char = chardata[f][c]
896 if char and type(char.unicode) == "table" then
897 setlistcolor(info,c_ligature)
898 setlisttransparency(info,c_ligature_d)
899 else
900 setlistcolor(info,c_glyph)
901 setlisttransparency(info,c_glyph_d)
902 end
903 info = new_hlist(info)
904 setattr(info,a_layer,l_glyph)
905 local info = setlink(current,new_kern(-wd),info)
906 info = hpack_nodes(info)
907 setwidth(info,wd)
908 if next then
909 setlink(info,next)
910 end
911 if prev then
912 setlink(prev,info)
913 end
914 if head == current then
915 return info, info
916 else
917 return head, info
918 end
919 else
920 return head, current
921 end
922 end
923
924 function visualizers.setruledglyph(f)
925 ruledglyph = f or ruledglyph
926 end
927
928end
929
930local ruledglue do
931
932 local gluecodes = nodes.gluecodes
933
934 local userskip_code = gluecodes.userskip
935 local spaceskip_code = gluecodes.spaceskip
936 local xspaceskip_code = gluecodes.xspaceskip
937 local zerospaceskip_code = gluecodes.zerospaceskip or gluecodes.userskip
938
939 local leftskip_code = gluecodes.leftskip
940 local rightskip_code = gluecodes.rightskip
941 local parfillleftskip_code = gluecodes.parfillleftskip or parfillskip_code
942 local parfillrightskip_code = gluecodes.parfillrightskip or parfillskip_code
943 local indentskip_code = gluecodes.indentskip
944 local correctionskip_code = gluecodes.correctionskip
945
946 local g_cache_v = caches["vglue"]
947 local g_cache_h = caches["hglue"]
948
949 local tags = {
950
951 [gluecodes.lineskip] = "LI",
952 [gluecodes.baselineskip] = "BS",
953 [gluecodes.parskip] = "PS",
954 [gluecodes.abovedisplayskip] = "DA",
955 [gluecodes.belowdisplayskip] = "DB",
956 [gluecodes.abovedisplayshortskip] = "SA",
957 [gluecodes.belowdisplayshortskip] = "SB",
958 [gluecodes.topskip] = "TS",
959 [gluecodes.splittopskip] = "ST",
960 [gluecodes.tabskip] = "AS",
961 [gluecodes.lefthangskip] = "LH",
962 [gluecodes.righthangskip] = "RH",
963 [gluecodes.thinmuskip] = "MS",
964 [gluecodes.medmuskip] = "MM",
965 [gluecodes.thickmuskip] = "ML",
966 [gluecodes.intermathskip] = "IM",
967 [gluecodes.keepskip or 99] = "KS",
968 [gluecodes.mathskip] = "MT",
969 [gluecodes.leaders] = "NL",
970 [gluecodes.cleaders] = "CL",
971 [gluecodes.xleaders] = "XL",
972 [gluecodes.gleaders] = "GL",
973
974
975 [leftskip_code] = "LS",
976 [rightskip_code] = "RS",
977 [spaceskip_code] = "SP",
978 [xspaceskip_code] = "XS",
979 [zerospaceskip_code] = "ZS",
980 [parfillleftskip_code] = "PL",
981 [parfillrightskip_code] = "PR",
982 [indentskip_code] = "IN",
983 [correctionskip_code] = "CS",
984 }
985
986
987
988 ruledglue = function(head,current,vertical,parent)
989 local subtype = getsubtype(current)
990 local width = effectiveglue(current,parent)
991 local amount = formatters["%s:%0.3f"](tags[subtype] or (vertical and "VS") or "HS",width*pt_factor)
992 local info = (vertical and g_cache_v or g_cache_h)[amount]
993 if info then
994
995 else
996 if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then
997 info = sometext(amount,l_glue,c_space)
998 elseif subtype == leftskip_code or subtype == rightskip_code then
999 info = sometext(amount,l_glue,c_skip_a)
1000 elseif subtype == parfillleftskip_code or subtype == parfillrightskip_code or subtype == indentskip_code or subtype == correctionskip_code then
1001 info = sometext(amount,l_glue,c_indent)
1002 elseif subtype == userskip_code then
1003 if width > 0 then
1004 info = sometext(amount,l_glue,c_positive)
1005 elseif width < 0 then
1006 info = sometext(amount,l_glue,c_negative)
1007 else
1008 info = sometext(amount,l_glue,c_zero)
1009 end
1010 else
1011 info = sometext(amount,l_glue,c_skip_b)
1012 end
1013 (vertical and g_cache_v or g_cache_h)[amount] = info
1014 end
1015 info = copylist(info)
1016 if vertical then
1017 info = vpack_nodes(info)
1018 end
1019 head, current = insertnodebefore(head,current,info)
1020 return head, getnext(current)
1021 end
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043 local g_cache_s = caches["space"]
1044 local g_cache_x = caches["xspace"]
1045
1046 ruledspace = function(head,current,parent)
1047 local subtype = getsubtype(current)
1048 if subtype == spaceskip_code or subtype == xspaceskip_code or subtype == zerospaceskip_code then
1049 local width = effectiveglue(current,parent)
1050 local info
1051 if subtype == spaceskip_code then
1052 info = g_cache_s[width]
1053 if not info then
1054 info = someblob("SP",l_glue,c_space,nil,width)
1055 g_cache_s[width] = info
1056 end
1057 else
1058 info = g_cache_x[width]
1059 if not info then
1060 info = someblob("XS",l_glue,c_space_x,nil,width)
1061 g_cache_x[width] = info
1062 end
1063 end
1064 info = copylist(info)
1065 head, current = insertnodebefore(head,current,info)
1066 return head, getnext(current)
1067 else
1068 return head, current
1069 end
1070 end
1071
1072end
1073
1074local ruledkern do
1075
1076 local k_cache_v = caches["vkern"]
1077 local k_cache_h = caches["hkern"]
1078
1079 ruledkern = function(head,current,vertical,mk)
1080 local kern = getkern(current)
1081 local cache = vertical and k_cache_v or k_cache_h
1082 local info = cache[kern]
1083 if not info then
1084 local amount = formatters["%s:%0.3f"](vertical and "VK" or (mk and "MK") or "HK",kern*pt_factor)
1085 if kern > 0 then
1086 info = sometext(amount,l_kern,c_positive)
1087 elseif kern < 0 then
1088 info = sometext(amount,l_kern,c_negative)
1089 else
1090 info = sometext(amount,l_kern,c_zero)
1091 end
1092 cache[kern] = info
1093 end
1094 info = copylist(info)
1095 if vertical then
1096 info = vpack_nodes(info)
1097 end
1098 head, current = insertnodebefore(head,current,info)
1099 return head, getnext(current)
1100 end
1101
1102end
1103
1104local ruleditalic do
1105
1106 local i_cache = caches["italic"]
1107
1108 ruleditalic = function(head,current)
1109 local kern = getkern(current)
1110 local info = i_cache[kern]
1111 if not info then
1112 local amount = formatters["%s:%0.3f"]("IC",kern*pt_factor)
1113 if kern > 0 then
1114 info = sometext(amount,l_kern,c_positive)
1115 elseif kern < 0 then
1116 info = sometext(amount,l_kern,c_negative)
1117 else
1118 info = sometext(amount,l_kern,c_zero)
1119 end
1120 i_cache[kern] = info
1121 end
1122 info = copylist(info)
1123 head, current = insertnodebefore(head,current,info)
1124 return head, getnext(current)
1125 end
1126
1127end
1128
1129local ruledmarginkern do
1130
1131 local m_cache = caches["marginkern"]
1132
1133 ruledmarginkern = function(head,current)
1134 local kern = getkern(current)
1135 local info = m_cache[kern]
1136 if not info then
1137 local amount = formatters["%s:%0.3f"]("MK",kern*pt_factor)
1138 if kern > 0 then
1139 info = sometext(amount,l_marginkern,c_positive)
1140 elseif kern < 0 then
1141 info = sometext(amount,l_marginkern,c_negative)
1142 else
1143 info = sometext(amount,l_marginkern,c_zero)
1144 end
1145 m_cache[kern] = info
1146 end
1147 info = copylist(info)
1148 head, current = insertnodebefore(head,current,info)
1149 return head, getnext(current)
1150 end
1151
1152end
1153
1154local ruledmathlistkern do
1155
1156 local l_cache = caches["mathlistkern"]
1157
1158 ruledmathlistkern = function(head,current)
1159 local kern = getkern(current)
1160 local info = l_cache[kern]
1161 if not info then
1162 local amount = formatters["%s:%0.3f"]("LK",kern*pt_factor)
1163 if kern > 0 then
1164 info = sometext(amount,l_mathlistkern,c_positive)
1165 elseif kern < 0 then
1166 info = sometext(amount,l_mathlistkern,c_negative)
1167 else
1168 info = sometext(amount,l_mathlistkern,c_zero)
1169 end
1170 l_cache[kern] = info
1171 end
1172 info = copylist(info)
1173 head, current = insertnodebefore(head,current,info)
1174 return head, getnext(current)
1175 end
1176
1177end
1178
1179local ruleddiscretionary do
1180
1181 local d_cache = caches["discretionary"]
1182
1183 ruleddiscretionary = function(head,current)
1184 local d = d_cache[true]
1185 if not the_discretionary then
1186 local rule = new_rule(4*emwidth/fraction,4*exheight,exheight)
1187 local kern = new_kern(-2*emwidth/fraction)
1188 setlink(kern,rule)
1189 setcolor(rule,c_discretionary_d)
1190 settransparency(rule,c_discretionary_d)
1191 setattr(rule,a_layer,l_discretionary)
1192 d = new_hlist(kern)
1193 d_cache[true] = d
1194 end
1195 insertnodeafter(head,current,copylist(d))
1196 return head, current
1197 end
1198
1199end
1200
1201local ruledpenalty do
1202
1203 local p_cache_v = caches["vpenalty"]
1204 local p_cache_h = caches["hpenalty"]
1205
1206 local raisepenalties = false
1207
1208 directives.register("visualizers.raisepenalties",function(v) raisepenalties = v end)
1209
1210 ruledpenalty = function(head,current,vertical)
1211 local penalty = getpenalty(current)
1212 local info = (vertical and p_cache_v or p_cache_h)[penalty]
1213 if info then
1214
1215 else
1216 local amount = formatters["%s:%s"](vertical and "VP" or "HP",penalty)
1217 if penalty > 0 then
1218 info = sometext(amount,l_penalty,c_positive)
1219 elseif penalty < 0 then
1220 info = sometext(amount,l_penalty,c_negative)
1221 else
1222 info = sometext(amount,l_penalty,c_zero)
1223 end
1224 (vertical and p_cache_v or p_cache_h)[penalty] = info
1225 end
1226 info = copylist(info)
1227 if vertical then
1228 info = vpack_nodes(info)
1229 elseif raisepenalties then
1230 setshift(info,-65536*4)
1231 end
1232 head, current = insertnodebefore(head,current,info)
1233 return head, getnext(current)
1234 end
1235
1236end
1237
1238do
1239
1240 local disc_code = nodecodes.disc
1241 local kern_code = nodecodes.kern
1242 local glyph_code = nodecodes.glyph
1243 local glue_code = nodecodes.glue
1244 local penalty_code = nodecodes.penalty
1245 local whatsit_code = nodecodes.whatsit
1246 local user_code = nodecodes.user
1247 local math_code = nodecodes.math
1248 local hlist_code = nodecodes.hlist
1249 local vlist_code = nodecodes.vlist
1250 local marginkern_code = nodecodes.marginkern
1251 local mathlistkern_code = nodecodes.mathlistkern
1252 local dir_code = nodecodes.dir
1253 local par_code = nodecodes.par
1254
1255 local kerncodes = nodes.kerncodes
1256 local fontkern_code = kerncodes.fontkern
1257 local italickern_code = kerncodes.italiccorrection
1258 local leftmarginkern_code = kerncodes.leftmarginkern
1259 local rightmarginkern_code = kerncodes.rightmarginkern
1260 local mathlistkern_code = kerncodes.mathlistkern
1261
1262
1263 local listcodes = nodes.listcodes
1264 local linelist_code = listcodes.line
1265
1266 local cache
1267
1268 local function visualize(head,vertical,forced,parent)
1269 local trace_hbox = false
1270 local trace_vbox = false
1271 local trace_vtop = false
1272 local trace_kern = false
1273 local trace_glue = false
1274 local trace_penalty = false
1275 local trace_fontkern = false
1276 local trace_strut = false
1277 local trace_whatsit = false
1278 local trace_glyph = false
1279 local trace_simple = false
1280 local trace_user = false
1281 local trace_math = false
1282 local trace_italic = false
1283 local trace_origin = false
1284 local trace_discretionary = false
1285 local trace_expansion = false
1286 local trace_line = false
1287 local trace_space = false
1288 local trace_depth = false
1289 local trace_dir = false
1290 local trace_par = false
1291 local current = head
1292 local previous = nil
1293 local attr = unsetvalue
1294 local prev_trace_fontkern = nil
1295 local prev_trace_italic = nil
1296 local prev_trace_marginkern = nil
1297
1298 local prev_trace_expansion = nil
1299
1300 while current do
1301 local id = getid(current)
1302 local a = forced or getattr(current,a_visual) or unsetvalue
1303 local subtype
1304 if a ~= attr then
1305 prev_trace_fontkern = trace_fontkern
1306 prev_trace_italic = trace_italic
1307 prev_trace_marginkern = trace_marginkern
1308
1309 prev_trace_expansion = trace_expansion
1310 attr = a
1311 if a == unsetvalue then
1312 trace_hbox = false
1313 trace_vbox = false
1314 trace_vtop = false
1315 trace_kern = false
1316 trace_glue = false
1317 trace_penalty = false
1318 trace_fontkern = false
1319 trace_strut = false
1320 trace_whatsit = false
1321 trace_glyph = false
1322 trace_simple = false
1323 trace_user = false
1324 trace_math = false
1325 trace_italic = false
1326 trace_origin = false
1327 trace_discretionary = false
1328 trace_expansion = false
1329 trace_line = false
1330 trace_space = false
1331 trace_depth = false
1332 trace_marginkern = false
1333 trace_mathlistkern = false
1334 trace_dir = false
1335 trace_par = false
1336 if id == kern_code then
1337 goto kern
1338 else
1339 goto list
1340 end
1341 else
1342
1343 trace_hbox = band(a,0x000001) ~= 0
1344 trace_vbox = band(a,0x000002) ~= 0
1345 trace_vtop = band(a,0x000004) ~= 0
1346 trace_kern = band(a,0x000008) ~= 0
1347 trace_glue = band(a,0x000010) ~= 0
1348 trace_penalty = band(a,0x000020) ~= 0
1349 trace_fontkern = band(a,0x000040) ~= 0
1350 trace_strut = band(a,0x000080) ~= 0
1351 trace_whatsit = band(a,0x000100) ~= 0
1352 trace_glyph = band(a,0x000200) ~= 0
1353 trace_simple = band(a,0x000400) ~= 0
1354 trace_user = band(a,0x000800) ~= 0
1355 trace_math = band(a,0x001000) ~= 0
1356 trace_italic = band(a,0x002000) ~= 0
1357 trace_origin = band(a,0x004000) ~= 0
1358 trace_discretionary = band(a,0x008000) ~= 0
1359 trace_expansion = band(a,0x010000) ~= 0
1360 trace_line = band(a,0x020000) ~= 0
1361 trace_space = band(a,0x040000) ~= 0
1362 trace_depth = band(a,0x080000) ~= 0
1363 trace_marginkern = band(a,0x100000) ~= 0
1364 trace_mathlistkern = band(a,0x200000) ~= 0
1365 trace_dir = band(a,0x400000) ~= 0
1366 trace_whatsit = band(a,0x800000) ~= 0
1367 end
1368 elseif a == unsetvalue then
1369 goto list
1370 end
1371 if trace_strut then
1372 setattr(current,a_layer,l_strut)
1373 elseif id == glyph_code then
1374 if trace_glyph then
1375 head, current = ruledglyph(head,current,previous)
1376 end
1377 if trace_expansion then
1378 head, current = glyphexpansion(head,current)
1379 end
1380 elseif id == disc_code then
1381 if trace_discretionary then
1382 head, current = ruleddiscretionary(head,current)
1383 end
1384 local pre, post, replace = getdisc(current)
1385 if pre then
1386 pre = visualize(pre,false,a,parent)
1387 end
1388 if post then
1389 post = visualize(post,false,a,parent)
1390 end
1391 if replace then
1392 replace = visualize(replace,false,a,parent)
1393 end
1394 setdisc(current,pre,post,replace)
1395 elseif id == kern_code then
1396 goto kern
1397 elseif id == glue_code then
1398 local content = getleader(current)
1399 if content then
1400 setleader(current,visualize(content,false,nil,parent))
1401 elseif trace_glue then
1402 head, current = ruledglue(head,current,vertical,parent)
1403 elseif trace_space then
1404 head, current = ruledspace(head,current,parent)
1405 end
1406 elseif id == penalty_code then
1407 if trace_penalty then
1408 head, current = ruledpenalty(head,current,vertical)
1409 end
1410 elseif id == hlist_code or id == vlist_code then
1411 goto list
1412 elseif id == whatsit_code then
1413 if trace_whatsit then
1414 head, current = whatsit(head,current)
1415 end
1416 elseif id == user_code then
1417 if trace_user then
1418 head, current = user(head,current)
1419 end
1420 elseif id == math_code then
1421 if trace_math then
1422 head, current = math(head,current)
1423 end
1424 elseif id == marginkern_code then
1425 if trace_kern then
1426 head, current = ruledkern(head,current,vertical,true)
1427 end
1428 elseif id == dir_code then
1429 if trace_dir then
1430 head, current = dir(head,current)
1431 end
1432 elseif id == par_code then
1433 if trace_par then
1434 head, current = par(head,current)
1435 end
1436 end
1437 goto next
1438 ::kern::
1439 subtype = getsubtype(current)
1440 if subtype == fontkern_code then
1441 if trace_fontkern or prev_trace_fontkern then
1442 head, current = fontkern(head,current)
1443 end
1444 if trace_expansion or prev_trace_expansion then
1445 head, current = kernexpansion(head,current)
1446 end
1447 elseif subtype == italickern_code then
1448 if trace_italic or prev_trace_italic then
1449 head, current = italickern(head,current)
1450 elseif trace_kern then
1451 head, current = ruleditalic(head,current)
1452 end
1453 elseif subtype == leftmarginkern_code or subtype == rightmarginkern_code then
1454 if trace_marginkern or prev_trace_marginkern then
1455 head, current = marginkern(head,current)
1456 elseif trace_kern then
1457 head, current = ruledmarginkern(head,current)
1458 end
1459 elseif subtype == mathlistkern_code then
1460 if trace_mathlist then
1461 head, current = mathlistkern(head,current)
1462 elseif trace_kern then
1463 head, current = ruledmathlistkern(head,current)
1464 end
1465 else
1466 if trace_kern then
1467 head, current = ruledkern(head,current,vertical)
1468 end
1469 end
1470 goto next;
1471 ::list::
1472 if id == hlist_code then
1473 local content = getlist(current)
1474 if content then
1475 setlist(current,visualize(content,false,nil,current))
1476 end
1477 if trace_depth then
1478 ruleddepth(current)
1479 end
1480 if trace_line and getsubtype(current) == linelist_code then
1481 head, current = ruledbox(head,current,false,l_line,"L__",trace_simple,previous,trace_origin,parent)
1482 elseif trace_hbox then
1483 head, current = ruledbox(head,current,false,l_hbox,"H__",trace_simple,previous,trace_origin,parent)
1484 end
1485 elseif id == vlist_code then
1486 local content = getlist(current)
1487 if content then
1488 setlist(current,visualize(content,true,nil,current))
1489 end
1490 if trace_vtop then
1491 head, current = ruledbox(head,current,true,l_vtop,"_T_",trace_simple,previous,trace_origin,parent)
1492 elseif trace_vbox then
1493 head, current = ruledbox(head,current,true,l_vbox,"__V",trace_simple,previous,trace_origin,parent)
1494 end
1495 end
1496 ::next::
1497 previous = current
1498 current = getnext(current)
1499 end
1500 return head
1501 end
1502
1503 local function cleanup()
1504 for tag, cache in next, caches do
1505 for k, v in next, cache do
1506 flushnodelist(v)
1507 end
1508 end
1509 cleanup = function()
1510 report_visualize("error, duplicate cleanup")
1511 end
1512 end
1513
1514 luatex.registerstopactions(cleanup)
1515
1516 function visualizers.handler(head)
1517 if usedfont then
1518 starttiming(visualizers)
1519 head = visualize(head,true)
1520 stoptiming(visualizers)
1521 return head, true
1522 else
1523 return head, false
1524 end
1525 end
1526
1527 function visualizers.box(n)
1528 if usedfont then
1529 starttiming(visualizers)
1530 local box = getbox(n)
1531 if box then
1532 setlist(box,visualize(getlist(box),getid(box) == vlist_code))
1533 end
1534 stoptiming(visualizers)
1535 return head, true
1536 else
1537 return head, false
1538 end
1539 end
1540
1541end
1542
1543do
1544
1545 local hlist_code = nodecodes.hlist
1546 local vlist_code = nodecodes.vlist
1547 local nextnode = nuts.traversers.node
1548
1549 local last = nil
1550 local used = nil
1551
1552 local mark = {
1553 "trace:1", "trace:2", "trace:3",
1554 "trace:4", "trace:5", "trace:6",
1555 "trace:7",
1556 }
1557
1558 local function markfonts(list)
1559 for n, id in nextnode, list do
1560 if id == glyph_code then
1561 local font = getfont(n)
1562 local okay = used[font]
1563 if not okay then
1564 last = last + 1
1565 okay = mark[last]
1566 used[font] = okay
1567 end
1568 setcolor(n,okay)
1569 elseif id == hlist_code or id == vlist_code then
1570 markfonts(getlist(n))
1571 end
1572 end
1573 end
1574
1575 function visualizers.markfonts(list)
1576 last, used = 0, { }
1577 markfonts(type(n) == "number" and getlist(getbox(n)) or n)
1578 end
1579
1580end
1581
1582statistics.register("visualization time",function()
1583 if enabled then
1584
1585 return formatters["%s seconds"](statistics.elapsedtime(visualizers))
1586 end
1587end)
1588
1589
1590
1591do
1592
1593 local implement = interfaces.implement
1594
1595 implement {
1596 name = "setvisual",
1597 arguments = "string",
1598 actions = visualizers.setvisual
1599 }
1600
1601 implement {
1602 name = "setvisuals",
1603 arguments = "string",
1604 actions = visualizers.setvisual
1605 }
1606
1607 implement {
1608 name = "getvisual",
1609 arguments = "string",
1610 actions = { setvisual, context }
1611 }
1612
1613 implement {
1614 name = "setvisuallayer",
1615 arguments = "string",
1616 actions = visualizers.setlayer
1617 }
1618
1619 implement {
1620 name = "markvisualfonts",
1621 arguments = "integer",
1622 actions = visualizers.markfonts
1623 }
1624
1625 implement {
1626 name = "setvisualfont",
1627 arguments = "integer",
1628 actions = visualizers.setfont
1629 }
1630
1631end
1632
1633
1634
1635do
1636
1637 local function make(str,forecolor,rulecolor,layer)
1638 if initialize then
1639 initialize()
1640 end
1641 local rule = new_rule(emwidth/fraction,exheight,4*exheight)
1642 setcolor(rule,rulecolor)
1643 settransparency(rule,rulecolor)
1644 local info
1645 if str == "" then
1646 info = new_hlist(rule)
1647 else
1648 local text = hpack_string(str,usedfont)
1649 local list = getlist(text)
1650 setlistcolor(list,textcolor)
1651 setlisttransparency(list,textcolor)
1652 setshift(text,3.5 * exheight)
1653 info = new_hlist(setlink(rule,text))
1654 end
1655 setattr(info,a_layer,layer)
1656 return info
1657 end
1658
1659 function visualizers.register(name,textcolor,rulecolor)
1660 if rawget(layers,name) then
1661
1662 return
1663 end
1664 local cache = caches[name]
1665 local layer = layers[name]
1666 if not textcolor then
1667 textcolor = c_text_d
1668 end
1669 if not rulecolor then
1670 rulecolor = c_origin_d
1671 end
1672 return function(str)
1673 if not str then
1674 str = ""
1675 end
1676 local info = cache[str]
1677 if not info then
1678 info = make(str,textcolor,rulecolor,layer)
1679 cache[str] = info
1680 end
1681 return copy_node(info)
1682 end
1683 end
1684
1685end
1686 |