1if not modules then modules = { } end modules ['spac-prf'] = {
2 version = 1.001,
3 comment = "companion to spac-prf.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13local unpack, rawget = unpack, rawget
14
15local formatters = string.formatters
16
17local nodecodes = nodes.nodecodes
18local gluecodes = nodes.gluecodes
19
20local glyph_code = nodecodes.glyph
21local disc_code = nodecodes.disc
22local kern_code = nodecodes.kern
23local penalty_code = nodecodes.penalty
24local glue_code = nodecodes.glue
25local hlist_code = nodecodes.hlist
26local vlist_code = nodecodes.vlist
27local unset_code = nodecodes.unset
28local math_code = nodecodes.math
29local rule_code = nodecodes.rule
30local marginkern_code = nodecodes.marginkern
31
32local leaders_code = gluecodes.leaders
33local lineskip_code = gluecodes.lineskip
34local baselineskip_code = gluecodes.baselineskip
35
36local strutrule_code = nodes.rulecodes.strut
37local linelist_code = nodes.listcodes.line
38
39local texlists = tex.lists
40local settexattribute = tex.setattribute
41local texgetdimen = tex.getdimen
42
43local d_strutht = tex.isdimen("strutht")
44local d_strutdp = tex.isdimen("strutdp")
45
46local newindex = lua.newindex
47
48local nuts = nodes.nuts
49local tonut = nodes.tonut
50local tonode = nuts.tonode
51
52local getreplace = nuts.getreplace
53local getattr = nuts.getattr
54local getid = nuts.getid
55local getboth = nuts.getboth
56local getnext = nuts.getnext
57local getprev = nuts.getprev
58local getsubtype = nuts.getsubtype
59local getlist = nuts.getlist
60local gettexbox = nuts.getbox
61local getwhd = nuts.getwhd
62local getglue = nuts.getglue
63local getkern = nuts.getkern
64local getshift = nuts.getshift
65local getwidth = nuts.getwidth
66local getheight = nuts.getheight
67local getdepth = nuts.getdepth
68local getboxglue = nuts.getboxglue
69local effectiveglue = nuts.effectiveglue
70local findattribute = nuts.findattribute
71local getspeciallist = nuts.getspeciallist
72
73local getlistdimensions = nuts.getlistdimensions
74
75local nextnode = nuts.traversers.node
76local nextglue = nuts.traversers.glue
77
78local setlink = nuts.setlink
79local setlist = nuts.setlist
80local setattr = nuts.setattr
81local setwhd = nuts.setwhd
82local setshift = nuts.setshift
83local setwidth = nuts.setwidth
84local setheight = nuts.setheight
85local setdepth = nuts.setdepth
86
87local properties = nodes.properties.data
88local setprop = nuts.setprop
89local getprop = nuts.getprop
90local theprop = nuts.theprop
91
92local floor = math.floor
93local ceiling = math.ceil
94local min = math.min
95local max = math.max
96
97local new_rule = nuts.pool.rule
98local new_glue = nuts.pool.glue
99local new_kern = nuts.pool.kern
100local hpack_nodes = nuts.hpack
101local find_node_tail = nuts.tail
102local setglue = nuts.setglue
103
104local a_visual = attributes.private("visual")
105local a_snapmethod = attributes.private("snapmethod")
106local a_profilemethod = attributes.private("profilemethod")
107
108
109local variables = interfaces.variables
110local v_none = variables.none
111local v_fixed = variables.fixed
112local v_strict = variables.strict
113
114local setcolor = nodes.tracers.colors.set
115local settransparency = nodes.tracers.transparencies.set
116
117local enableaction = nodes.tasks.enableaction
118
119local profiling = { }
120builders.profiling = profiling
121
122local report = logs.reporter("profiling")
123
124local show_profile = false trackers.register("profiling.show", function(v) show_profile = v end)
125local trace_profile = false trackers.register("profiling.trace",function(v) trace_profile = v end)
126
127local function getprofile(line,step)
128
129
130
131
132 local line = tonut(line)
133 local current = getlist(line)
134
135 if not current then
136 return
137 end
138
139
140
141 local heights = { }
142 local depths = { }
143 local width = 0
144 local position = 0
145 local step = step or 65536
146 local margin = step / 4
147 local min = 0
148 local max = ceiling(getwidth(line)/step) + 1
149 local wd = 0
150 local ht = 0
151 local dp = 0
152
153 for i=min,max do
154 heights[i] = 0
155 depths [i] = 0
156 end
157
158
159
160 local function process(current)
161 for current, id, subtype in nextnode, current do
162 if id == glyph_code then
163 wd, ht, dp = getwhd(current)
164 elseif id == kern_code then
165 wd = getkern(current)
166 ht = 0
167 dp = 0
168 elseif id == disc_code then
169 local replace = getreplace(current)
170 if replace then
171 process(replace)
172 end
173 goto done
174 elseif id == glue_code then
175 wd = effectiveglue(current, line)
176
177 if subtype >= leaders_code then
178 local leader = getleader(current)
179 local w
180 w, ht, dp = getwhd(leader)
181 else
182 ht = 0
183 dp = 0
184 end
185 elseif id == hlist_code then
186
187
188 local shift = getshift(current)
189 local w, h, d = getwhd(current)
190 if getprop(current,"specialcontent") then
191
192 wd = w
193 ht = 0
194 dp = 0
195 else
196 wd = w
197 ht = h - shift
198 dp = d + shift
199 end
200 elseif id == vlist_code or id == unset_code then
201 local shift = getshift(current)
202 wd, ht, dp = getwhd(current)
203 elseif id == rule_code then
204 wd, ht, dp = getwhd(current)
205 elseif id == math_code then
206
207 wd = getkern(current) + getwidth(current)
208 ht = 0
209 dp = 0
210 else
211 goto done
212 end
213
214 position = width
215 width = position + wd
216 p = floor((position - margin)/step + 0.5)
217 w = floor((width + margin)/step - 0.5)
218 if p < 0 then
219 p = 0
220 end
221 if w < 0 then
222 w = 0
223 end
224 if p > w then
225 w, p = p, w
226 end
227 if w > max then
228 for i=max+1,w+1 do
229 heights[i] = 0
230 depths [i] = 0
231 end
232 max = w
233 end
234 for i=p,w do
235 if ht > heights[i] then
236 heights[i] = ht
237 end
238 if dp > depths[i] then
239 depths[i] = dp
240 end
241 end
242 ::done::
243 end
244 end
245
246 process(current)
247
248 return {
249 heights = heights,
250 depths = depths,
251 min = min,
252 max = max,
253 step = step,
254 }
255
256end
257
258profiling.get = getprofile
259
260local function getpagelist()
261 local pagehead = texlists.pagehead
262 if pagehead then
263 pagehead = tonut(texlists.pagehead)
264 pagetail = find_node_tail(pagehead)
265 else
266 pagetail = nil
267 end
268 return pagehead, pagetail
269end
270
271local function setprofile(n,step)
272 local p = rawget(properties,n)
273 if p then
274 local pp = p.profile
275 if not pp then
276 pp = getprofile(n,step)
277 p.profile = pp
278 end
279 return pp
280 else
281 local pp = getprofile(n,step)
282 properties[n] = { profile = pp }
283 return pp
284 end
285end
286
287local function hasprofile(n)
288 local p = rawget(properties,n)
289 if p then
290 return p.profile
291 end
292end
293
294local function addstring(height,depth)
295 local typesetters = nuts.typesetters
296 local hashes = fonts.hashes
297 local infofont = fonts.infofont()
298 local emwidth = hashes.emwidths [infofont]
299 local exheight = hashes.exheights[infofont]
300 local httext = height
301 local dptext = depth
302 local httext = typesetters.tohpack(height,infofont)
303 local dptext = typesetters.tohpack(depth,infofont)
304 setshift(httext,- 1.2 * exheight)
305 setshift(dptext, 0.6 * exheight)
306 local text = hpack_nodes(setlink(
307 new_kern(-getwidth(httext)-emwidth),
308 httext,
309 new_kern(-getwidth(dptext)),
310 dptext
311 ))
312 setwhd(text,0,0,0)
313 return text
314end
315
316local function addprofile(node,profile,step)
317
318 local line = tonut(node)
319
320 if not profile then
321 profile = setprofile(line,step)
322 end
323
324 if not profile then
325 report("some error")
326 return node
327 end
328
329 if profile.shown then
330 return node
331 end
332
333 local list = getlist(line)
334 profile.shown = true
335
336 local heights = profile.heights
337 local depths = profile.depths
338 local step = profile.step
339
340 local head = nil
341 local tail = nil
342
343 local lastht = 0
344 local lastdp = 0
345 local lastwd = 0
346
347 local visual = "f:s:t"
348
349 local function progress()
350 if lastwd == 0 then
351 return
352 end
353 local what = nil
354
355 if lastht == 0 and lastdp == 0 then
356 what = new_kern(lastwd)
357 else
358 what = new_rule(lastwd,lastht,lastdp)
359 setcolor(what,visual)
360 settransparency(what,visual)
361 end
362 if tail then
363 setlink(tail,what)
364 else
365 head = what
366 end
367 tail = what
368 end
369
370
371
372 for i=profile.min,profile.max do
373 local ht = heights[i]
374 local dp = depths[i]
375 if ht ~= lastht or dp ~= lastdp and lastwd > 0 then
376 progress()
377 lastht = ht
378 lastdp = dp
379 lastwd = step
380 else
381 lastwd = lastwd + step
382 end
383 end
384 if lastwd > 0 then
385 progress()
386 end
387
388 local rule = hpack_nodes(head)
389
390 setwhd(rule,0,0,0)
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405 setlink(rule,list)
406 setlist(line,rule)
407
408end
409
410profiling.add = addprofile
411
412local methods = { }
413
414local function getdelta(t_profile,b_profile)
415 local t_heights = t_profile.heights
416 local t_depths = t_profile.depths
417 local t_max = t_profile.max
418 local b_heights = b_profile.heights
419 local b_depths = b_profile.depths
420 local b_max = b_profile.max
421
422 local max = t_max
423 local delta = 0
424
425 if t_max > b_max then
426 for i=b_max+1,t_max do
427 b_depths [i] = 0
428 b_heights[i] = 0
429 end
430 max = t_max
431 elseif b_max > t_max then
432 for i=t_max+1,b_max do
433 t_depths [i] = 0
434 t_heights[i] = 0
435 end
436 max = b_max
437 end
438
439 for i=0,max do
440 local ht = b_heights[i]
441 local dp = t_depths[i]
442 local hd = ht + dp
443 if hd > delta then
444 delta = hd
445 end
446 end
447
448 return delta
449end
450
451
452
453
454
455
456
457
458
459
460
461
462local function inject(top,bot,amount)
463 if amount ~= 0 then
464 local glue = new_glue(amount)
465
466 setattr(glue,a_profilemethod,0)
467
468 setattr(glue,a_visual,nodes.visualizers.modes.glue)
469
470 setlink(top,glue,bot)
471
472 report("injected correction %p at page",amount,tex.getcount("realpageno"))
473 end
474end
475
476methods[v_none] = function()
477 return false
478end
479
480methods[v_strict] = function(top,bot,t_profile,b_profile,specification)
481
482 local top = tonut(top)
483 local bot = tonut(bot)
484
485 local strutht = specification.height or texgetdimen(d_strutht)
486 local strutdp = specification.depth or texgetdimen(d_strutdp)
487 local lineheight = strutht + strutdp
488
489 local depth = getdepth(top)
490 local height = getheight(bot)
491 local total = depth + height
492 local distance = specification.distance or 0
493 local delta = lineheight - total
494
495
496
497
498 if delta >= distance then
499 inject(top,bot,delta)
500 return true
501 end
502
503 local delta = getdelta(t_profile,b_profile)
504 local skip = delta - total + distance
505
506
507
508
509 inject(top,bot,skip)
510 return true
511
512end
513
514
515
516methods[v_fixed] = function(top,bot,t_profile,b_profile,specification)
517
518 local top = tonut(top)
519 local bot = tonut(bot)
520
521 local strutht = specification.height or texgetdimen(d_strutht)
522 local strutdp = specification.depth or texgetdimen(d_strutdp)
523 local lineheight = strutht + strutdp
524
525 local depth = getdepth(top)
526 local height = getheight(bot)
527 local total = depth + height
528 local distance = specification.distance or 0
529 local delta = lineheight - total
530
531 local snapmethod = getattr(top,a_snapmethod)
532
533 if snapmethod then
534
535
536
537 if delta < lineheight then
538 setdepth(top,strutdp)
539 setheight(bot,strutht)
540 return true
541 end
542
543 local delta = getdelta(t_profile,b_profile)
544
545 local dp = strutdp
546 while depth > lineheight - strutdp do
547 depth = depth - lineheight
548 dp = dp + lineheight
549 end
550 setdepth(top,dp)
551 local ht = strutht
552 while height > lineheight - strutht do
553 height = height - lineheight
554 ht = ht + lineheight
555 end
556 setheight(bot,ht)
557 local lines = floor(delta/lineheight)
558 if lines > 0 then
559 inject(top,bot,-lines * lineheight)
560 end
561
562 return true
563
564 end
565
566 if total < lineheight then
567 setdepth(top,strutdp)
568 setheight(bot,strutht)
569 return true
570 end
571
572 if depth < strutdp then
573 setdepth(top,strutdp)
574 total = total - depth + strutdp
575 end
576 if height < strutht then
577 setheight(bot,strutht)
578 total = total - height + strutht
579 end
580
581 local delta = getdelta(t_profile,b_profile)
582
583 local target = total - delta
584 local factor = specification.factor or 1
585 local step = lineheight / factor
586 local correction = 0
587 local nofsteps = 0
588 while correction < target - step - distance do
589 correction = correction + step
590 nofsteps = nofsteps + 1
591 end
592
593 if trace_profile then
594 report("top line : %s %05i > %s",t_profile.shown and "+" or "-",top,nodes.toutf(getlist(top)))
595 report("bottom line : %s %05i > %s",b_profile.shown and "+" or "-",bot,nodes.toutf(getlist(bot)))
596 report(" depth : %p",depth)
597 report(" height : %p",height)
598 report(" total : %p",total)
599 report(" lineheight : %p",lineheight)
600 report(" delta : %p",delta)
601 report(" target : %p",target)
602 report(" factor : %i",factor)
603 report(" distance : %p",distance)
604 report(" step : %p",step)
605 report(" nofsteps : %i",nofsteps)
606
607 report(" correction : %p",correction)
608 end
609
610 inject(top,bot,-correction)
611
612 return true
613
614end
615
616function profiling.distance(top,bot,specification)
617 local step = specification.step
618 local method = specification.method
619 local ptop = getprofile(top,step)
620 local pbot = getprofile(bot,step)
621 local action = methods[method or v_strict] or methods[v_strict]
622 return action(top,bot,ptop,pbot,specification)
623end
624
625local specifications = { }
626
627function profiling.fixedprofile(current)
628 local a = getattr(current,a_profilemethod)
629 if a then
630 local s = specifications[a]
631 if s then
632 return s.method == v_fixed
633 end
634 end
635 return false
636end
637
638local function profilelist(line,mvl)
639
640 local top = nil
641 local bot = nil
642
643 local t_profile = nil
644 local b_profile = nil
645
646 local specification = nil
647 local lastattr = nil
648 local method = nil
649 local action = nil
650
651 local distance = 0
652 local lastglue = nil
653
654 local pagehead = nil
655 local pagetail = nil
656
657 if mvl then
658
659 pagehead, pagetail = getpagelist()
660
661 if pagetail then
662 for current, id, subtype in nextnode, pagetail do
663 if id == hlist_code then
664 if subtype == linelist_code then
665 t_profile = hasprofile(current)
666 if t_profile then
667 top = current
668 end
669 end
670 break
671 elseif id == glue_code then
672 local wd = getwidth(current)
673 if not wd or wd == 0 then
674
675 else
676 break
677 end
678 elseif id == penalty_code then
679
680 else
681 break
682 end
683 end
684 end
685
686 end
687
688 for current, id, subtype in nextnode, line do
689
690 local attr = getattr(current,a_profilemethod)
691
692 if attr then
693
694 if attr ~= lastattr then
695 specification = specifications[attr]
696 method = specification and specification.method
697 action = method and methods[method] or methods[v_strict]
698 lastattr = attr
699 end
700
701 if id == hlist_code then
702 if subtype == linelist_code then
703 if top == current then
704
705 bot = nil
706 elseif top then
707 bot = current
708 b_profile = setprofile(bot)
709 if show_profile then
710 addprofile(bot,b_profile)
711 end
712 if not t_profile.done then
713 if action then
714 local ok = action(top,bot,t_profile,b_profile,specification)
715 if ok and lastglue and distance ~= 0 then
716 setglue(lastglue)
717 end
718 end
719 t_profile.done = true
720 end
721 top = bot
722 bot = nil
723 t_profile = b_profile
724 b_profile = nil
725 distance = 0
726 else
727 top = current
728 t_profile = setprofile(top)
729 bot = nil
730 if show_profile then
731 addprofile(top,t_profile)
732 end
733 end
734 else
735 top = nil
736 bot = nil
737 end
738 elseif id == glue_code then
739 if top then
740
741 local wd = getwidth(current)
742 if wd > 0 then
743 distance = wd
744 lastglue = current
745 elseif wd < 0 then
746 top = nil
747 bot = nil
748 else
749
750 end
751
752
753
754
755 else
756 top = nil
757 bot = nil
758 end
759 elseif id == penalty_code then
760
761 else
762 top = nil
763 bot = nil
764 end
765 else
766 top = nil
767 bot = nil
768 end
769 end
770 if top then
771 t_profile = setprofile(top)
772 if show_profile then
773 addprofile(top,t_profile)
774 end
775 end
776end
777
778profiling.list = profilelist
779
780local enabled = false
781
782
783
784function profiling.set(specification)
785 if not enabled then
786 enableaction("mvlbuilders", "builders.profiling.pagehandler")
787
788
789 enabled = true
790 end
791 local n = #specifications + 1
792 specifications[n] = specification
793 settexattribute(a_profilemethod,n)
794end
795
796function profiling.profilebox(specification)
797 local boxnumber = specification.box
798 local current = getlist(gettexbox(boxnumber))
799 local top = nil
800 local bot = nil
801 local t_profile = nil
802 local b_profile = nil
803 local method = specification and specification.method
804 local action = method and methods[method] or methods[v_strict]
805 local lastglue = nil
806 local distance = 0
807 for current, id, subtype in nextnode, current do
808 if id == hlist_code then
809 if subtype == linelist_code then
810 if top then
811 bot = current
812 b_profile = setprofile(bot)
813 if show_profile then
814 addprofile(bot,b_profile)
815 end
816 if not t_profile.done then
817 if action then
818 local ok = action(top,bot,t_profile,b_profile,specification)
819 if ok and lastglue and distance ~= 0 then
820 setglue(lastglue)
821 end
822 end
823 t_profile.done = true
824 end
825 top = bot
826 t_profile = b_profile
827 b_profile = nil
828 distance = 0
829 else
830 top = current
831 t_profile = setprofile(top)
832 if show_profile then
833 addprofile(top,t_profile)
834 end
835 bot = nil
836 end
837 else
838 top = nil
839 bot = nil
840 end
841 elseif id == glue_code then
842 if subtype == lineskip_code or subtype == baselineskip_code then
843 if top then
844 local wd = getwidth(current)
845 if wd > 0 then
846 distance = wd
847 lastglue = current
848 elseif wd < 0 then
849 top = nil
850 bot = nil
851 else
852
853 end
854 else
855 top = nil
856 bot = nil
857 end
858 else
859 top = nil
860 bot = nil
861 end
862 elseif id == penalty_code then
863
864 else
865 top = nil
866 bot = nil
867 end
868 end
869
870 if top then
871 t_profile = setprofile(top)
872 if show_profile then
873 addprofile(top,t_profile)
874 end
875 end
876
877end
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894function profiling.pagehandler(head)
895 if head then
896 profilelist(head,true)
897 end
898 return head
899end
900
901interfaces.implement {
902 name = "setprofile",
903 actions = profiling.set,
904 arguments = {
905 {
906 { "name" },
907 { "height", "dimen" },
908 { "depth", "dimen" },
909 { "distance", "dimen" },
910 { "factor", "integer" },
911 { "lines", "integer" },
912 { "method" }
913 }
914 }
915}
916
917interfaces.implement {
918 name = "profilebox",
919 actions = profiling.profilebox,
920 arguments = {
921 {
922 { "box", "integer" },
923 { "height", "dimen" },
924 { "depth", "dimen" },
925 { "distance", "dimen" },
926 { "factor", "integer" },
927 { "lines", "integer" },
928 { "method" }
929 }
930 }
931}
932
933
934
935
936
937
938
939do
940
941
942
943 local a_lineprofile = attributes.private("lineprofile")
944
945 local registervalue = attributes.registervalue
946 local getvalue = attributes.getvalue
947 local texsetattribute = tex.setattribute
948
949 local function getdepthprofile(line,step,margin,max,list)
950
951 local width = 0
952 local position = 0
953 local profile = newindex(max+2,0)
954 local wd = 0
955 local ht = 0
956 local dp = 0
957
958 profile[0] = 0
959
960 local function process(current)
961 for current, id, subtype in nextnode, current do
962 if id == glyph_code then
963 wd, ht, dp = getwhd(current)
964 elseif id == kern_code then
965 wd = getkern(current)
966 dp = 0
967 elseif id == disc_code then
968 local replace = getreplace(current)
969 if replace then
970 process(replace)
971 end
972 goto done
973 elseif id == glue_code then
974 wd = effectiveglue(current, line)
975
976 if subtype >= leaders_code then
977 local leader = getleader(current)
978 local w
979 w, ht, dp = getwhd(leader)
980 else
981 dp = 0
982 end
983 elseif id == hlist_code then
984 local w, h, d, shift = getlistdimensions(current)
985 if getprop(current,"specialcontent") then
986
987 wd = w
988 dp = 0
989 else
990 wd = w
991 dp = d + shift
992 end
993 elseif id == vlist_code then
994 local shift
995 wd, ht, dp, shift = getlistdimensions(current)
996 dp = dp + shift
997 elseif id == rule_code then
998 if subtype == strutrule_code then
999 dp = 0
1000 else
1001 wd, ht, dp = getwhd(current)
1002 end
1003 elseif id == math_code then
1004
1005 wd = getkern(current) + getwidth(current)
1006 dp = 0
1007 else
1008 goto done
1009 end
1010
1011 position = width
1012 width = position + wd
1013 p = floor((position - margin)/step + 0.5)
1014 w = floor((width + margin)/step - 0.5)
1015 if p < 0 then
1016 p = 0
1017 end
1018 if w < 0 then
1019 w = 0
1020 end
1021 if p > w then
1022 w, p = p, w
1023 end
1024 if w > max then
1025 for i=max+1,w+1 do
1026 profile[i] = 0
1027 end
1028 max = w
1029 end
1030 for i=p,w do
1031 if dp > profile[i] then
1032 profile[i] = dp
1033 end
1034 end
1035 ::done::
1036 end
1037 end
1038
1039 process(list)
1040
1041 return profile
1042
1043 end
1044
1045 local function getheightprofile(line,step,margin,max,list)
1046
1047 local width = 0
1048 local position = 0
1049 local profile = newindex(max+2,0)
1050 local wd = 0
1051 local ht = 0
1052 local dp = 0
1053
1054 profile[0] = 0
1055
1056 local function process(current)
1057 for current, id, subtype in nextnode, current do
1058 if id == glyph_code then
1059 wd, ht, dp = getwhd(current)
1060 elseif id == kern_code then
1061 wd = getkern(current)
1062 ht = 0
1063 elseif id == disc_code then
1064 local replace = getreplace(current)
1065 if replace then
1066 process(replace)
1067 end
1068 goto done
1069 elseif id == glue_code then
1070 wd = effectiveglue(current, line)
1071
1072 if subtype >= leaders_code then
1073 local leader = getleader(current)
1074 local w
1075 w, ht, dp = getwhd(leader)
1076 else
1077 ht = 0
1078 end
1079 elseif id == hlist_code then
1080 local w, h, d, shift = getlistdimensions(current)
1081 if getprop(current,"specialcontent") then
1082
1083 wd = w
1084 ht = 0
1085 else
1086 wd = w
1087 ht = h - shift
1088 end
1089 elseif id == vlist_code then
1090 local shift
1091 wd, ht, dp, shift = getlistdimensions(current)
1092 ht = ht - shift
1093 elseif id == rule_code then
1094 if subtype == strutrule_code then
1095 ht = 0
1096 else
1097 wd, ht, dp = getwhd(current)
1098 end
1099 elseif id == math_code then
1100
1101 wd = getkern(current) + getwidth(current)
1102 ht = 0
1103 else
1104 goto done
1105 end
1106
1107 position = width
1108 width = position + wd
1109 p = floor((position - margin)/step + 0.5)
1110 w = floor((width + margin)/step - 0.5)
1111 if p < 0 then
1112 p = 0
1113 end
1114 if w < 0 then
1115 w = 0
1116 end
1117 if p > w then
1118 w, p = p, w
1119 end
1120 if w > max then
1121 for i=max+1,w+1 do
1122 profile[i] = 0
1123 end
1124 max = w
1125 end
1126 for i=p,w do
1127 if ht > profile[i] then
1128 profile[i] = ht
1129 end
1130 end
1131 ::done::
1132 end
1133 end
1134
1135 process(list)
1136
1137 return profile
1138
1139 end
1140
1141 local show_lineprofile = false
1142 local show_linedetails = false
1143
1144 trackers.register("profiling.lines.show", function(v)
1145 local visualizers = nodes.visualizers
1146 glue_mode = visualizers.modes.glue
1147 line_mode = visualizers.modes.line
1148 show_lineprofile = v
1149 visualizers.enable()
1150 end)
1151
1152 trackers.register("profiling.lines.details", function(v)
1153 show_linedetail = v
1154 end)
1155
1156 local defaultstep = 65536 * 2
1157 local defaultmethod = "a"
1158 local defaultfactor = 1
1159
1160 local v_yes = interfaces.variables.yes
1161
1162
1163
1164
1165 function profilelines(list,prev)
1166
1167 if not list then
1168 return
1169 end
1170
1171 local _, start = findattribute(list,a_lineprofile)
1172 if not start then
1173 return
1174 end
1175
1176
1177 for current, subtype in nextglue, start do
1178 if subtype == lineskip_code and not getprop(current,"profiled") then
1179 local detail = getattr(current,a_lineprofile)
1180 if detail then
1181 local amount = getwidth(current)
1182 if amount > 0 then
1183 detail = getvalue(a_lineprofile,detail) or { }
1184
1185 local top, bot = getboth(current)
1186 setprop(current,"profiled",amount)
1187 if not top and prev and detail.paragraph == v_yes then
1188 top = prev
1189 end
1190 if top then
1191 if getid(top) == penalty_code then
1192 top = getprev(top)
1193 end
1194 if top and bot then
1195 if getid(top) == hlist_code and getsubtype(top) == linelist_code then
1196 if getid(bot) == hlist_code and getsubtype(bot) == linelist_code then
1197 local toplist = getlist(top)
1198 local botlist = getlist(bot)
1199 if toplist and botlist then
1200
1201 local step = detail.step or defaultstep
1202 local factor = tonumber(detail.factor) or defaultfactor
1203 local method = detail.method or defaultmethod
1204 local margin = step / 4
1205
1206 if factor > 1 then
1207 factor = 1
1208 elseif factor <= 0 then
1209 factor = 0
1210 end
1211
1212 local natural = getdepth(top) + getheight(bot)
1213 local added = factor * amount
1214 local possible = natural - added
1215 local overshoot = 0
1216 local topmax = ceiling(getwidth(top)/step) + 1
1217 local botmax = ceiling(getwidth(bot)/step) + 1
1218
1219 local depths = getdepthprofile (top,step,margin,topmax,toplist)
1220 local heights = getheightprofile(bot,step,margin,botmax,botlist)
1221 local steps = min(#depths,#heights)
1222 for i=1,steps do
1223 local o = heights[i] + depths[i] - possible
1224 if o > overshoot then
1225
1226 overshoot = o
1227
1228
1229
1230 end
1231 end
1232
1233
1234
1235
1236 if overshoot ~= amount then
1237 setwidth(current,overshoot)
1238 if show_lineprofile then
1239 setattr(current,a_visual,glue_mode)
1240 setattr(bot,a_visual,line_mode)
1241 setattr(top,a_visual,line_mode)
1242 end
1243 if show_linedetail then
1244 report("lineskip changed from %p to %p on page %i",amount,overshoot,tex.getcount("realpageno"))
1245 end
1246 end
1247 end
1248 end
1249 end
1250 end
1251 end
1252 end
1253 end
1254 end
1255 prev = nil
1256 end
1257 end
1258
1259 builders.profiling.profilelines = profilelines
1260
1261 function profiling.boxlinehandler(head)
1262 if head then
1263 profilelines(head)
1264 end
1265 return head
1266 end
1267
1268 function profiling.pagelinehandler(head,...)
1269 if head then
1270 local h, t = getspeciallist("pagehead")
1271 profilelines(head,t)
1272 end
1273 return head
1274 end
1275
1276
1277
1278 function profiling.setlines(specification)
1279 if not enabled then
1280 enableaction("mvlbuilders", "builders.profiling.pagelinehandler")
1281 enableaction("vboxbuilders", "builders.profiling.boxlinehandler")
1282 enabled = true
1283 end
1284 texsetattribute(a_lineprofile,registervalue(a_lineprofile,specification))
1285 end
1286
1287 interfaces.implement {
1288 name = "setlineprofile",
1289 actions = profiling.setlines,
1290 arguments = {
1291 {
1292 { "name" },
1293 { "method" },
1294 { "step", "dimension" },
1295 { "factor" },
1296 { "paragraph" },
1297 }
1298 }
1299 }
1300
1301 interfaces.implement {
1302 name = "lineprofilebox",
1303 public = true,
1304 protected = true,
1305 actions = function(box)
1306 local okay = nuts.getbox(box)
1307 local list = getlist(okay)
1308 if list then
1309 profiling.boxlinehandler(list)
1310 end
1311 end,
1312 arguments = "integer"
1313 }
1314
1315end
1316 |