1 if 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
19local listcodes = nodes.listcodes
20
21local glyph_code = nodecodes.glyph
22local disc_code = nodecodes.disc
23local kern_code = nodecodes.kern
24local penalty_code = nodecodes.penalty
25local glue_code = nodecodes.glue
26local hlist_code = nodecodes.hlist
27local vlist_code = nodecodes.vlist
28local unset_code = nodecodes.unset
29local math_code = nodecodes.math
30local rule_code = nodecodes.rule
31local marginkern_code = nodecodes.marginkern
32
33local leaders_code = gluecodes.leaders
34local lineskip_code = gluecodes.lineskip
35local baselineskip_code = gluecodes.baselineskip
36
37local linelist_code = listcodes.line
38
39local texlists = tex.lists
40local settexattribute = tex.setattribute
41
42local nuts = nodes.nuts
43local tonut = nodes.tonut
44local tonode = nuts.tonode
45
46local getreplace = nuts.getreplace
47local getattr = nuts.getattr
48local getid = nuts.getid
49local getnext = nuts.getnext
50local getprev = nuts.getprev
51local getsubtype = nuts.getsubtype
52local getlist = nuts.getlist
53local gettexbox = nuts.getbox
54local getwhd = nuts.getwhd
55local getglue = nuts.getglue
56local getkern = nuts.getkern
57local getshift = nuts.getshift
58local getwidth = nuts.getwidth
59local getheight = nuts.getheight
60local getdepth = nuts.getdepth
61local getboxglue = nuts.getboxglue
62
63local setlink = nuts.setlink
64local setlist = nuts.setlist
65local setattr = nuts.setattr
66local setwhd = nuts.setwhd
67local setshift = nuts.setshift
68local setwidth = nuts.setwidth
69local setheight = nuts.setheight
70local setdepth = nuts.setdepth
71
72local properties = nodes.properties.data
73local setprop = nuts.setprop
74local getprop = nuts.getprop
75local theprop = nuts.theprop
76
77local floor = math.floor
78local ceiling = math.ceil
79
80local new_rule = nuts.pool.rule
81local new_glue = nuts.pool.glue
82local new_kern = nuts.pool.kern
83local hpack_nodes = nuts.hpack
84local find_node_tail = nuts.tail
85local setglue = nuts.setglue
86
87local a_visual = attributes.private("visual")
88local a_snapmethod = attributes.private("snapmethod")
89local a_profilemethod = attributes.private("profilemethod")
90
91
92local variables = interfaces.variables
93local v_none = variables.none
94local v_fixed = variables.fixed
95local v_strict = variables.strict
96
97local setcolor = nodes.tracers.colors.set
98local settransparency = nodes.tracers.transparencies.set
99
100local enableaction = nodes.tasks.enableaction
101
102local profiling = { }
103builders.profiling = profiling
104
105local report = logs.reporter("profiling")
106
107local show_profile = false trackers.register("profiling.show", function(v) show_profile = v end)
108local trace_profile = false trackers.register("profiling.trace",function(v) trace_profile = v end)
109
110local function getprofile(line,step)
111
112
113
114
115 local line = tonut(line)
116 local current = getlist(line)
117
118 if not current then
119 return
120 end
121
122 local glue_set, glue_order, glue_sign = getboxglue(line)
123
124 local heights = { }
125 local depths = { }
126 local width = 0
127 local position = 0
128 local step = step or 65536
129 local margin = step / 4
130 local min = 0
131 local max = ceiling(getwidth(line)/step) + 1
132 local wd = 0
133 local ht = 0
134 local dp = 0
135
136 for i=min,max do
137 heights[i] = 0
138 depths [i] = 0
139 end
140
141
142
143 local function progress()
144 position = width
145 width = position + wd
146 p = floor((position - margin)/step + 0.5)
147 w = floor((width + margin)/step - 0.5)
148 if p < 0 then
149 p = 0
150 end
151 if w < 0 then
152 w = 0
153 end
154 if p > w then
155 w, p = p, w
156 end
157 if w > max then
158 for i=max+1,w+1 do
159 heights[i] = 0
160 depths [i] = 0
161 end
162 max = w
163 end
164 for i=p,w do
165 if ht > heights[i] then
166 heights[i] = ht
167 end
168 if dp > depths[i] then
169 depths[i] = dp
170 end
171 end
172 end
173
174 local function process(current)
175 while current do
176 local id = getid(current)
177 if id == glyph_code then
178 wd, ht, dp = getwhd(current)
179 progress()
180 elseif id == kern_code then
181 wd = getkern(current)
182 ht = 0
183 dp = 0
184 progress()
185 elseif id == disc_code then
186 local replace = getreplace(current)
187 if replace then
188 process(replace)
189 end
190 elseif id == glue_code then
191 local width, stretch, shrink, stretch_order, shrink_order = getglue(current)
192 if glue_sign == 1 then
193 if stretch_order == glue_order then
194 wd = width + stretch * glue_set
195 else
196 wd = width
197 end
198 elseif glue_sign == 2 then
199 if shrink_order == glue_order then
200 wd = width - shrink * glue_set
201 else
202 wd = width
203 end
204 else
205 wd = width
206 end
207 if getsubtype(current) >= leaders_code then
208 local leader = getleader(current)
209 local w
210 w, ht, dp = getwhd(leader)
211 else
212 ht = 0
213 dp = 0
214 end
215 progress()
216 elseif id == hlist_code then
217
218 local shift = getshift(current)
219 local w, h, d = getwhd(current)
220
221 if getprop(current,"specialcontent") then
222
223 wd = w
224 ht = 0
225 dp = 0
226 else
227 wd = w
228 ht = h - shift
229 dp = d + shift
230 end
231 progress()
232 elseif id == vlist_code or id == unset_code then
233 local shift = getshift(current)
234 wd, ht, dp = getwhd(current)
235 progress()
236 elseif id == rule_code then
237 wd, ht, dp = getwhd(current)
238 progress()
239 elseif id == math_code then
240 wd = getkern(current) + getwidth(current)
241 ht = 0
242 dp = 0
243 progress()
244 elseif id == marginkern_code then
245
246 wd = getwidth(current)
247 ht = 0
248 dp = 0
249 progress()
250 else
251
252 end
253 current = getnext(current)
254 end
255 end
256
257 process(current)
258
259 return {
260 heights = heights,
261 depths = depths,
262 min = min,
263 max = max,
264 step = step,
265 }
266
267end
268
269profiling.get = getprofile
270
271local function getpagelist()
272 local pagehead = texlists.page_head
273 if pagehead then
274 pagehead = tonut(texlists.page_head)
275 pagetail = find_node_tail(pagehead)
276 else
277 pagetail = nil
278 end
279 return pagehead, pagetail
280end
281
282local function setprofile(n,step)
283 local p = rawget(properties,n)
284 if p then
285 local pp = p.profile
286 if not pp then
287 pp = getprofile(n,step)
288 p.profile = pp
289 end
290 return pp
291 else
292 local pp = getprofile(n,step)
293 properties[n] = { profile = pp }
294 return pp
295 end
296end
297
298local function hasprofile(n)
299 local p = rawget(properties,n)
300 if p then
301 return p.profile
302 end
303end
304
305local function addstring(height,depth)
306 local typesetters = nuts.typesetters
307 local hashes = fonts.hashes
308 local infofont = fonts.infofont()
309 local emwidth = hashes.emwidths [infofont]
310 local exheight = hashes.exheights[infofont]
311 local httext = height
312 local dptext = depth
313 local httext = typesetters.tohpack(height,infofont)
314 local dptext = typesetters.tohpack(depth,infofont)
315 setshift(httext,- 1.2 * exheight)
316 setshift(dptext, 0.6 * exheight)
317 local text = hpack_nodes(setlink(
318 new_kern(-getwidth(httext)-emwidth),
319 httext,
320 new_kern(-getwidth(dptext)),
321 dptext
322 ))
323 setwhd(text,0,0,0)
324 return text
325end
326
327local function addprofile(node,profile,step)
328
329 local line = tonut(node)
330
331 if not profile then
332 profile = setprofile(line,step)
333 end
334
335 if not profile then
336 report("some error")
337 return node
338 end
339
340 if profile.shown then
341 return node
342 end
343
344 local list = getlist(line)
345 profile.shown = true
346
347 local heights = profile.heights
348 local depths = profile.depths
349 local step = profile.step
350
351 local head = nil
352 local tail = nil
353
354 local lastht = 0
355 local lastdp = 0
356 local lastwd = 0
357
358 local visual = "f:s:t"
359
360 local function progress()
361 if lastwd == 0 then
362 return
363 end
364 local what = nil
365 if lastht == 0 and lastdp == 0 then
366 what = new_kern(lastwd)
367 else
368 what = new_rule(lastwd,lastht,lastdp)
369 setcolor(what,visual)
370 settransparency(what,visual)
371 end
372 if tail then
373 setlink(tail,what)
374 else
375 head = what
376 end
377 tail = what
378 end
379
380
381
382 for i=profile.min,profile.max do
383 local ht = heights[i]
384 local dp = depths[i]
385 if ht ~= lastht or dp ~= lastdp and lastwd > 0 then
386 progress()
387 lastht = ht
388 lastdp = dp
389 lastwd = step
390 else
391 lastwd = lastwd + step
392 end
393 end
394 if lastwd > 0 then
395 progress()
396 end
397
398 local rule = hpack_nodes(head)
399
400 setwhd(rule,0,0,0)
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415 setlink(rule,list)
416 setlist(line,rule)
417
418end
419
420profiling.add = addprofile
421
422local methods = { }
423
424local function getdelta(t_profile,b_profile)
425 local t_heights = t_profile.heights
426 local t_depths = t_profile.depths
427 local t_max = t_profile.max
428 local b_heights = b_profile.heights
429 local b_depths = b_profile.depths
430 local b_max = b_profile.max
431
432 local max = t_max
433 local delta = 0
434
435 if t_max > b_max then
436 for i=b_max+1,t_max do
437 b_depths [i] = 0
438 b_heights[i] = 0
439 end
440 max = t_max
441 elseif b_max > t_max then
442 for i=t_max+1,b_max do
443 t_depths [i] = 0
444 t_heights[i] = 0
445 end
446 max = b_max
447 end
448
449 for i=0,max do
450 local ht = b_heights[i]
451 local dp = t_depths[i]
452 local hd = ht + dp
453 if hd > delta then
454 delta = hd
455 end
456 end
457
458 return delta
459end
460
461
462
463
464
465
466
467
468
469
470
471
472local function inject(top,bot,amount)
473 local glue = new_glue(amount)
474
475 setattr(glue,a_profilemethod,0)
476 setattr(glue,a_visual,getattr(top,a_visual))
477
478 setlink(top,glue,bot)
479end
480
481methods[v_none] = function()
482 return false
483end
484
485methods[v_strict] = function(top,bot,t_profile,b_profile,specification)
486
487 local top = tonut(top)
488 local bot = tonut(bot)
489
490 local strutht = specification.height or texdimen.strutht
491 local strutdp = specification.depth or texdimen.strutdp
492 local lineheight = strutht + strutdp
493
494 local depth = getdepth(top)
495 local height = getheight(bot)
496 local total = depth + height
497 local distance = specification.distance or 0
498 local delta = lineheight - total
499
500
501
502
503 if delta >= distance then
504 inject(top,bot,delta)
505 return true
506 end
507
508 local delta = getdelta(t_profile,b_profile)
509 local skip = delta - total + distance
510
511
512
513
514 inject(top,bot,skip)
515 return true
516
517end
518
519
520
521methods[v_fixed] = function(top,bot,t_profile,b_profile,specification)
522
523 local top = tonut(top)
524 local bot = tonut(bot)
525
526 local strutht = specification.height or texdimen.strutht
527 local strutdp = specification.depth or texdimen.strutdp
528 local lineheight = strutht + strutdp
529
530 local depth = getdepth(top)
531 local height = getheight(bot)
532 local total = depth + height
533 local distance = specification.distance or 0
534 local delta = lineheight - total
535
536 local snapmethod = getattr(top,a_snapmethod)
537
538 if snapmethod then
539
540
541
542 if delta < lineheight then
543 setdepth(top,strutdp)
544 setheight(bot,strutht)
545 return true
546 end
547
548 local delta = getdelta(t_profile,b_profile)
549
550 local dp = strutdp
551 while depth > lineheight - strutdp do
552 depth = depth - lineheight
553 dp = dp + lineheight
554 end
555 setdepth(top,dp)
556 local ht = strutht
557 while height > lineheight - strutht do
558 height = height - lineheight
559 ht = ht + lineheight
560 end
561 setheight(bot,ht)
562 local lines = floor(delta/lineheight)
563 if lines > 0 then
564 inject(top,bot,-lines * lineheight)
565 end
566
567 return true
568
569 end
570
571 if total < lineheight then
572 setdepth(top,strutdp)
573 setheight(bot,strutht)
574 return true
575 end
576
577 if depth < strutdp then
578 setdepth(top,strutdp)
579 total = total - depth + strutdp
580 end
581 if height < strutht then
582 setheight(bot,strutht)
583 total = total - height + strutht
584 end
585
586 local delta = getdelta(t_profile,b_profile)
587
588 local target = total - delta
589 local factor = specification.factor or 1
590 local step = lineheight / factor
591 local correction = 0
592 local nofsteps = 0
593 while correction < target - step - distance do
594 correction = correction + step
595 nofsteps = nofsteps + 1
596 end
597
598 if trace_profile then
599 report("top line : %s %05i > %s",t_profile.shown and "+" or "-",top,nodes.toutf(getlist(top)))
600 report("bottom line : %s %05i > %s",b_profile.shown and "+" or "-",bot,nodes.toutf(getlist(bot)))
601 report(" depth : %p",depth)
602 report(" height : %p",height)
603 report(" total : %p",total)
604 report(" lineheight : %p",lineheight)
605 report(" delta : %p",delta)
606 report(" target : %p",target)
607 report(" factor : %i",factor)
608 report(" distance : %p",distance)
609 report(" step : %p",step)
610 report(" nofsteps : %i",nofsteps)
611
612 report(" correction : %p",correction)
613 end
614
615 inject(top,bot,-correction)
616
617 return true
618
619end
620
621function profiling.distance(top,bot,specification)
622 local step = specification.step
623 local method = specification.method
624 local ptop = getprofile(top,step)
625 local pbot = getprofile(bot,step)
626 local action = methods[method or v_strict] or methods[v_strict]
627 return action(top,bot,ptop,pbot,specification)
628end
629
630local specifications = { }
631
632function profiling.fixedprofile(current)
633 local a = getattr(current,a_profilemethod)
634 if a then
635 local s = specifications[a]
636 if s then
637 return s.method == v_fixed
638 end
639 end
640 return false
641end
642
643local function profilelist(line,mvl)
644
645 local current = line
646
647 local top = nil
648 local bot = nil
649
650 local t_profile = nil
651 local b_profile = nil
652
653 local specification = nil
654 local lastattr = nil
655 local method = nil
656 local action = nil
657
658 local distance = 0
659 local lastglue = nil
660
661 local pagehead = nil
662 local pagetail = nil
663
664 if mvl then
665
666 pagehead, pagetail = getpagelist()
667
668 if pagetail then
669 local current = pagetail
670 while current do
671 local id = getid(current)
672 if id == hlist_code then
673 local subtype = getsubtype(current)
674 if subtype == linelist_code then
675 t_profile = hasprofile(current)
676 if t_profile then
677 top = current
678 end
679 end
680 break
681 elseif id == glue_code then
682 local wd = getwidth(current)
683 if not wd or wd == 0 then
684
685 else
686 break
687 end
688 elseif id == penalty_code then
689
690 else
691 break
692 end
693 current = getnext(current)
694 end
695 end
696
697 end
698
699 while current do
700
701 local attr = getattr(current,a_profilemethod)
702
703 if attr then
704
705 if attr ~= lastattr then
706 specification = specifications[attr]
707 method = specification and specification.method
708 action = method and methods[method] or methods[v_strict]
709 lastattr = attr
710 end
711
712 local id = getid(current)
713
714 if id == hlist_code then
715 local subtype = getsubtype(current)
716 if subtype == linelist_code then
717 if top == current then
718
719 bot = nil
720 elseif top then
721 bot = current
722 b_profile = setprofile(bot)
723 if show_profile then
724 addprofile(bot,b_profile)
725 end
726 if not t_profile.done then
727 if action then
728 local ok = action(top,bot,t_profile,b_profile,specification)
729 if ok and lastglue and distance ~= 0 then
730 setglue(lastglue)
731 end
732 end
733 t_profile.done = true
734 end
735 top = bot
736 bot = nil
737 t_profile = b_profile
738 b_profile = nil
739 distance = 0
740 else
741 top = current
742 t_profile = setprofile(top)
743 bot = nil
744 if show_profile then
745 addprofile(top,t_profile)
746 end
747 end
748 else
749 top = nil
750 bot = nil
751 end
752 elseif id == glue_code then
753 if top then
754 local subtype = getsubtype(current)
755
756 local wd = getwidth(current)
757 if wd > 0 then
758 distance = wd
759 lastglue = current
760 elseif wd < 0 then
761 top = nil
762 bot = nil
763 else
764
765 end
766
767
768
769
770 else
771 top = nil
772 bot = nil
773 end
774 elseif id == penalty_code then
775
776 else
777 top = nil
778 bot = nil
779 end
780 else
781 top = nil
782 bot = nil
783 end
784 current = getnext(current)
785 end
786 if top then
787 t_profile = setprofile(top)
788 if show_profile then
789 addprofile(top,t_profile)
790 end
791 end
792end
793
794profiling.list = profilelist
795
796local enabled = false
797
798function profiling.set(specification)
799 if not enabled then
800 enableaction("mvlbuilders", "builders.profiling.pagehandler")
801
802
803 enabled = true
804 end
805 local n = #specifications + 1
806 specifications[n] = specification
807 settexattribute(a_profilemethod,n)
808end
809
810function profiling.profilebox(specification)
811 local boxnumber = specification.box
812 local current = getlist(gettexbox(boxnumber))
813 local top = nil
814 local bot = nil
815 local t_profile = nil
816 local b_profile = nil
817 local method = specification and specification.method
818 local action = method and methods[method] or methods[v_strict]
819 local lastglue = nil
820 local distance = 0
821 while current do
822 local id = getid(current)
823 if id == hlist_code then
824 local subtype = getsubtype(current)
825 if subtype == linelist_code then
826 if top then
827 bot = current
828 b_profile = setprofile(bot)
829 if show_profile then
830 addprofile(bot,b_profile)
831 end
832 if not t_profile.done then
833 if action then
834 local ok = action(top,bot,t_profile,b_profile,specification)
835 if ok and lastglue and distance ~= 0 then
836 setglue(lastglue)
837 end
838 end
839 t_profile.done = true
840 end
841 top = bot
842 t_profile = b_profile
843 b_profile = nil
844 distance = 0
845 else
846 top = current
847 t_profile = setprofile(top)
848 if show_profile then
849 addprofile(top,t_profile)
850 end
851 bot = nil
852 end
853 else
854 top = nil
855 bot = nil
856 end
857 elseif id == glue_code then
858 local subtype = getsubtype(current)
859 if subtype == lineskip_code or subtype == baselineskip_code then
860 if top then
861 local wd = getwidth(current)
862 if wd > 0 then
863 distance = wd
864 lastglue = current
865 elseif wd < 0 then
866 top = nil
867 bot = nil
868 else
869
870 end
871 else
872 top = nil
873 bot = nil
874 end
875 else
876 top = nil
877 bot = nil
878 end
879 elseif id == penalty_code then
880
881 else
882 top = nil
883 bot = nil
884 end
885 current = getnext(current)
886 end
887
888 if top then
889 t_profile = setprofile(top)
890 if show_profile then
891 addprofile(top,t_profile)
892 end
893 end
894
895end
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912function profiling.pagehandler(head)
913 if head then
914 profilelist(head,true)
915 end
916 return head
917end
918
919interfaces.implement {
920 name = "setprofile",
921 actions = profiling.set,
922 arguments = {
923 {
924 { "name" },
925 { "height", "dimen" },
926 { "depth", "dimen" },
927 { "distance", "dimen" },
928 { "factor", "integer" },
929 { "lines", "integer" },
930 { "method" }
931 }
932 }
933}
934
935interfaces.implement {
936 name = "profilebox",
937 actions = profiling.profilebox,
938 arguments = {
939 {
940 { "box", "integer" },
941 { "height", "dimen" },
942 { "depth", "dimen" },
943 { "distance", "dimen" },
944 { "factor", "integer" },
945 { "lines", "integer" },
946 { "method" }
947 }
948 }
949}
950 |