1if not modules then modules = { } end modules [ ' supp-box ' ] = {
2 version = 1 . 001 ,
3 comment = " companion to supp-box.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
11local report_hyphenation = logs . reporter ( " languages " , " hyphenation " )
12
13local tonumber , next , type = tonumber , next , type
14
15local lpegmatch = lpeg . match
16
17local tex = tex
18local context = context
19local nodes = nodes
20
21local implement = interfaces . implement
22
23local nodecodes = nodes . nodecodes
24
25local disc_code = nodecodes . disc
26local hlist_code = nodecodes . hlist
27local vlist_code = nodecodes . vlist
28local glue_code = nodecodes . glue
29local penalty_code = nodecodes . penalty
30local glyph_code = nodecodes . glyph
31local localpar_code = nodecodes . localpar
32
33local indent_code = nodes . listcodes . indent
34
35local hmode_code = tex . modelevels . horizontal
36
37local nuts = nodes . nuts
38local tonut = nuts . tonut
39local tonode = nuts . tonode
40
41
42local getnext = nuts . getnext
43local getprev = nuts . getprev
44local getboth = nuts . getboth
45local getdisc = nuts . getdisc
46local getid = nuts . getid
47local getsubtype = nuts . getsubtype
48local getlist = nuts . getlist
49local getattribute = nuts . getattribute
50local getbox = nuts . getbox
51local getdirection = nuts . getdirection
52local getwidth = nuts . getwidth
53local takebox = nuts . takebox
54
55
56local setlink = nuts . setlink
57local setboth = nuts . setboth
58local setnext = nuts . setnext
59local setprev = nuts . setprev
60local setbox = nuts . setbox
61local setlist = nuts . setlist
62local setdisc = nuts . setdisc
63local setwidth = nuts . setwidth
64local setheight = nuts . setheight
65local setdepth = nuts . setdepth
66local setshift = nuts . setshift
67local setsplit = nuts . setsplit
68local setattrlist = nuts . setattrlist
69
70local flush_node = nuts . flush_node
71local flush_list = nuts . flush_list
72local copy_node = nuts . copy
73local copy_list = nuts . copy_list
74local find_tail = nuts . tail
75local getdimensions = nuts . dimensions
76local hpack = nuts . hpack
77local vpack = nuts . vpack
78local traverse_id = nuts . traverse_id
79local traverse = nuts . traverse
80local free = nuts . free
81local findtail = nuts . tail
82
83local nextdisc = nuts . traversers . disc
84local nextdir = nuts . traversers . dir
85local nexthlist = nuts . traversers . hlist
86
87local listtoutf = nodes . listtoutf
88
89local nodepool = nuts . pool
90local new_penalty = nodepool . penalty
91local new_hlist = nodepool . hlist
92local new_glue = nodepool . glue
93
94local setlistcolor = nodes . tracers . colors . setlist
95
96local texget = tex . get
97local texgetbox = tex . getbox
98local texsetdimen = tex . setdimen
99local texgetnest = tex . getnest
100
101local function hyphenatedlist ( head , usecolor )
102 local current = head and tonut ( head )
103 while current do
104 local id = getid ( current )
105 local prev , next = getboth ( current )
106 if id = = disc_code then
107 local pre , post , replace = getdisc ( current )
108 if not usecolor then
109
110 elseif pre and post then
111 setlistcolor ( pre , " darkmagenta " )
112 setlistcolor ( post , " darkcyan " )
113 elseif pre then
114 setlistcolor ( pre , " darkyellow " )
115 elseif post then
116 setlistcolor ( post , " darkyellow " )
117 end
118 if replace then
119 flush_list ( replace )
120 end
121 setdisc ( current )
122 if pre then
123 setlink ( prev , new_penalty ( 10000 ) , pre )
124 setlink ( find_tail ( pre ) , current )
125 end
126 if post then
127 setlink ( current , new_penalty ( 10000 ) , post )
128 setlink ( find_tail ( post ) , next )
129 end
130 elseif id = = vlist_code or id = = hlist_code then
131 hyphenatedlist ( getlist ( current ) )
132 end
133 current = next
134 end
135end
136
137implement {
138 name = " hyphenatedlist " ,
139 arguments = { " integer " , " boolean " } ,
140 actions = function ( n , color )
141 local b = texgetbox ( n )
142 if b then
143 hyphenatedlist ( b . list , color )
144 end
145 end
146}
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161local function checkedlist ( list )
162 if type ( list ) = = " number " then
163 return getlist ( getbox ( tonut ( list ) ) )
164 else
165 return tonut ( list )
166 end
167end
168
169implement {
170 name = " showhyphenatedinlist " ,
171 arguments = " integer " ,
172 actions = function ( n )
173
174 local l = languages . hyphenators . handler ( tonode ( checkedlist ( n ) ) )
175 report_hyphenation ( " show: %s " , listtoutf ( l , false , true ) )
176 end
177}
178
179local function applytochars ( current , doaction , noaction , nested )
180 while current do
181 local id = getid ( current )
182 if nested and ( id = = hlist_code or id = = vlist_code ) then
183 context . beginhbox ( )
184 applytochars ( getlist ( current ) , doaction , noaction , nested )
185 context . endhbox ( )
186 elseif id ~ = glyph_code then
187 noaction ( tonode ( copy_node ( current ) ) )
188 else
189 doaction ( tonode ( copy_node ( current ) ) )
190 end
191 current = getnext ( current )
192 end
193end
194
195local function applytowords ( current , doaction , noaction , nested )
196 local start
197 while current do
198 local id = getid ( current )
199 if id = = glue_code then
200 if start then
201 doaction ( tonode ( copy_list ( start , current ) ) )
202 start = nil
203 end
204 noaction ( tonode ( copy_node ( current ) ) )
205 elseif nested and ( id = = hlist_code or id = = vlist_code ) then
206 context . beginhbox ( )
207 applytowords ( getlist ( current ) , doaction , noaction , nested )
208 context . egroup ( )
209 elseif not start then
210 start = current
211 end
212 current = getnext ( current )
213 end
214 if start then
215 doaction ( tonode ( copy_list ( start ) ) )
216 end
217end
218
219local methods = {
220 char = applytochars ,
221 characters = applytochars ,
222 word = applytowords ,
223 words = applytowords ,
224}
225
226implement {
227 name = " applytobox " ,
228 arguments = {
229 {
230 { " box " , " integer " } ,
231 { " command " } ,
232 { " method " } ,
233 { " nested " , " boolean " } ,
234 }
235 } ,
236 actions = function ( specification )
237 local list = checkedlist ( specification . box )
238 local action = methods [ specification . method or " char " ]
239 if list and action then
240 action ( list , context [ specification . command or " ruledhbox " ] , context , specification . nested )
241 end
242 end
243}
244
245local split_char = lpeg . Ct ( lpeg . C ( 1 ) ^ 0 )
246local split_word = lpeg . tsplitat ( lpeg . patterns . space )
247local split_line = lpeg . tsplitat ( lpeg . patterns . eol )
248
249local function processsplit ( specification )
250 local str = specification . data or " "
251 local command = specification . command or " ruledhbox "
252 local method = specification . method or " word "
253 local spaced = specification . spaced
254 if method = = " char " or method = = " character " then
255 local words = lpegmatch ( split_char , str )
256 for i = 1 , # words do
257 local word = words [ i ]
258 if word = = " " then
259 if spaced then
260 context . space ( )
261 end
262 elseif command then
263 context [ command ] ( word )
264 else
265 context ( word )
266 end
267 end
268 elseif method = = " word " then
269 local words = lpegmatch ( split_word , str )
270 for i = 1 , # words do
271 local word = words [ i ]
272 if spaced and i > 1 then
273 context . space ( )
274 end
275 if command then
276 context [ command ] ( word )
277 else
278 context ( word )
279 end
280 end
281 elseif method = = " line " then
282 local words = lpegmatch ( split_line , str )
283 for i = 1 , # words do
284 local word = words [ i ]
285 if spaced and i > 1 then
286 context . par ( )
287 end
288 if command then
289 context [ command ] ( word )
290 else
291 context ( word )
292 end
293 end
294 else
295 context ( str )
296 end
297end
298
299implement {
300 name = " processsplit " ,
301 actions = processsplit ,
302 arguments = {
303 {
304 { " data " } ,
305 { " command " } ,
306 { " method " } ,
307 { " spaced " , " boolean " } ,
308 }
309 }
310}
311
312local a_vboxtohboxseparator = attributes . private ( " vboxtohboxseparator " )
313
314implement {
315 name = " vboxlisttohbox " ,
316 arguments = { " integer " , " integer " , " dimen " } ,
317 actions = function ( original , target , inbetween )
318 local current = getlist ( getbox ( original ) )
319 local head = nil
320 local tail = nil
321 while current do
322 local id = getid ( current )
323 local next = getnext ( current )
324 if id = = hlist_code then
325 local list = getlist ( current )
326 if head then
327 if inbetween > 0 then
328 local n = new_glue ( 0 , 0 , inbetween )
329 setlink ( tail , n )
330 tail = n
331 end
332 setlink ( tail , list )
333 else
334 head = list
335 end
336 tail = find_tail ( list )
337
338 if getid ( tail ) = = hlist_code and getattribute ( tail , a_vboxtohboxseparator ) = = 1 then
339 local temp = tail
340 local prev = getprev ( tail )
341 if next then
342 local list = getlist ( tail )
343 setlink ( prev , list )
344 setlist ( tail )
345 tail = find_tail ( list )
346 else
347 tail = prev
348 end
349 flush_node ( temp )
350 end
351
352 setnext ( tail )
353 setlist ( current )
354 end
355 current = next
356 end
357 local result = new_hlist ( )
358 setlist ( result , head )
359 setbox ( target , result )
360
361 end
362}
363
364implement {
365 name = " hboxtovbox " ,
366 arguments = " integer " ,
367 actions = function ( n )
368 local b = getbox ( n )
369 local factor = texget ( " baselineskip " , false ) / texget ( " hsize " )
370 setdepth ( b , 0 )
371 setheight ( b , getwidth ( b ) * factor )
372 end
373}
374
375implement {
376 name = " boxtostring " ,
377 arguments = " integer " ,
378 actions = function ( n )
379 context . puretext ( nodes . toutf ( texgetbox ( n ) . list ) )
380 end
381}
382
383local function getnaturaldimensions ( n )
384 local w = 0
385 local h = 0
386 local d = 0
387 local l = getlist ( getbox ( n ) )
388 if l then
389 w , h , d = getdimensions ( l )
390 end
391 texsetdimen ( " lastnaturalboxwd " , w )
392 texsetdimen ( " lastnaturalboxht " , h )
393 texsetdimen ( " lastnaturalboxdp " , d )
394 return w , h , d
395end
396
397implement {
398 name = " getnaturaldimensions " ,
399 arguments = " integer " ,
400 actions = getnaturaldimensions
401}
402
403implement {
404 name = " naturalwd " ,
405 arguments = " integer " ,
406 actions = function ( n )
407 getnaturaldimensions ( n )
408 context . lastnaturalboxwd ( false )
409 end
410}
411
412implement {
413 name = " getnaturalwd " ,
414 arguments = " integer " ,
415 actions = function ( n )
416 local w = 0
417 local h = 0
418 local d = 0
419 local l = getlist ( getbox ( n ) )
420 if l then
421 w , h , d = getdimensions ( l )
422 end
423 context ( " \\dimexpr%i\\scaledpoint\\relax " , w )
424 end
425}
426
427local function setboxtonaturalwd ( n )
428 local old = takebox ( n )
429 local new = hpack ( getlist ( old ) )
430 setlist ( old , nil )
431 flush_node ( old )
432 setbox ( n , new )
433end
434
435implement {
436 name = " setnaturalwd " ,
437 arguments = " integer " ,
438 actions = setboxtonaturalwd
439}
440
441nodes . setboxtonaturalwd = setboxtonaturalwd
442
443local doifelse = commands . doifelse
444
445do
446
447 local dirvalues = nodes . dirvalues
448 local lefttoright_code = dirvalues . lefttoright
449 local righttoleft_code = dirvalues . righttoleft
450
451 local function firstdirinbox ( n )
452 local b = getbox ( n )
453 if b then
454 local l = getlist ( b )
455 if l then
456 for d in nextdir , l do
457 return getdirection ( d )
458 end
459 for h in nexthlist , l do
460 return getdirection ( h )
461 end
462 end
463 end
464 return lefttoright_code
465 end
466
467 nodes . firstdirinbox = firstdirinbox
468
469 implement {
470 name = " doifelserighttoleftinbox " ,
471 arguments = " integer " ,
472 actions = function ( n )
473 doifelse ( firstdirinbox ( n ) = = righttoleft_code )
474 end
475 }
476
477end
478
479
480
481do
482
483 local nuts = nodes . nuts
484 local tonode = nuts . tonode
485 local takebox = nuts . takebox
486 local flush_list = nuts . flush_list
487 local copy_list = nuts . copy_list
488 local getwhd = nuts . getwhd
489 local setbox = nuts . setbox
490 local new_hlist = nuts . pool . hlist
491
492 local boxes = { }
493 nodes . boxes = boxes
494 local cache = table . setmetatableindex ( " table " )
495 local report = logs . reporter ( " boxes " , " cache " )
496 local trace = false
497
498 trackers . register ( " nodes.boxes " , function ( v ) trace = v end )
499
500 function boxes . save ( category , name , b )
501 name = tonumber ( name ) or name
502 local b = takebox ( b )
503 if trace then
504 report ( " category %a, name %a, %s (%s) " , category , name , " save " , b and " content " or " empty " )
505 end
506 cache [ category ] [ name ] = b or false
507 end
508
509 function boxes . savenode ( category , name , n )
510 name = tonumber ( name ) or name
511 if trace then
512 report ( " category %a, name %a, %s (%s) " , category , name , " save " , n and " content " or " empty " )
513 end
514 cache [ category ] [ name ] = tonut ( n ) or false
515 end
516
517 function boxes . found ( category , name )
518 name = tonumber ( name ) or name
519 return cache [ category ] [ name ] and true or false
520 end
521
522 function boxes . direct ( category , name , copy )
523 name = tonumber ( name ) or name
524 local c = cache [ category ]
525 local b = c [ name ]
526 if not b then
527
528 elseif copy then
529 b = copy_list ( b )
530 else
531 c [ name ] = false
532 end
533 if trace then
534 report ( " category %a, name %a, %s (%s) " , category , name , " direct " , b and " content " or " empty " )
535 end
536 if b then
537 return tonode ( b )
538 end
539 end
540
541 function boxes . restore ( category , name , box , copy )
542 name = tonumber ( name ) or name
543 local c = cache [ category ]
544 local b = takebox ( box )
545 if b then
546 flush_list ( b )
547 end
548 local b = c [ name ]
549 if not b then
550
551 elseif copy then
552 b = copy_list ( b )
553 else
554 c [ name ] = false
555 end
556 if trace then
557 report ( " category %a, name %a, %s (%s) " , category , name , " restore " , b and " content " or " empty " )
558 end
559 setbox ( box , b or nil )
560 end
561
562 function boxes . dimensions ( category , name )
563 name = tonumber ( name ) or name
564 local b = cache [ category ] [ name ]
565 if b then
566 return getwhd ( b )
567 else
568 return 0 , 0 , 0
569 end
570 end
571
572 function boxes . reset ( category , name )
573 name = tonumber ( name ) or name
574 local c = cache [ category ]
575 if name and name ~ = " " then
576 local b = c [ name ]
577 if b then
578 flush_list ( b )
579 c [ name ] = false
580 end
581 if trace then
582 report ( " category %a, name %a, reset " , category , name )
583 end
584 else
585 for k , b in next , c do
586 if b then
587 flush_list ( b )
588 end
589 end
590 cache [ category ] = { }
591 if trace then
592 report ( " category %a, reset " , category )
593 end
594 end
595 end
596
597 implement {
598 name = " putboxincache " ,
599 arguments = { " string " , " string " , " integer " } ,
600 actions = boxes . save ,
601 }
602
603 implement {
604 name = " getboxfromcache " ,
605 arguments = { " string " , " string " , " integer " } ,
606 actions = boxes . restore ,
607 }
608
609 implement {
610 name = " directboxfromcache " ,
611 arguments = " 2 strings " ,
612 actions = { boxes . direct , context } ,
613
614 }
615
616 implement {
617 name = " directcopyboxfromcache " ,
618 arguments = { " string " , " string " , true } ,
619 actions = { boxes . direct , context } ,
620
621 }
622
623 implement {
624 name = " copyboxfromcache " ,
625 arguments = { " string " , " string " , " integer " , true } ,
626 actions = boxes . restore ,
627 }
628
629 implement {
630 name = " doifelseboxincache " ,
631 arguments = " 2 strings " ,
632 actions = { boxes . found , doifelse } ,
633 }
634
635 implement {
636 name = " resetboxesincache " ,
637 arguments = " string " ,
638 actions = boxes . reset ,
639 }
640
641end
642
643implement {
644 name = " lastlinewidth " ,
645 actions = function ( )
646 local head = tex . lists . page_head
647
648 context ( head and getdimensions ( getlist ( find_tail ( tonut ( tex . lists . page_head ) ) ) ) or 0 )
649 end
650}
651
652implement {
653 name = " shiftbox " ,
654 arguments = { " integer " , " dimension " } ,
655 actions = function ( n , d )
656 setshift ( getbox ( n ) , d )
657 end ,
658}
659
660implement { name = " vpackbox " , arguments = " integer " , actions = function ( n ) setbox ( n , ( vpack ( takebox ( n ) ) ) ) end }
661implement { name = " hpackbox " , arguments = " integer " , actions = function ( n ) setbox ( n , ( hpack ( takebox ( n ) ) ) ) end }
662
663implement { name = " vpackedbox " , arguments = " integer " , actions = function ( n ) context ( vpack ( takebox ( n ) ) ) end }
664implement { name = " hpackedbox " , arguments = " integer " , actions = function ( n ) context ( hpack ( takebox ( n ) ) ) end }
665
666implement {
667 name = " scangivendimensions " ,
668 public = true ,
669 protected = true ,
670 arguments = {
671 {
672 { " width " , " dimension " } ,
673 { " height " , " dimension " } ,
674 { " depth " , " dimension " } ,
675 } ,
676 } ,
677 actions = function ( t )
678 texsetdimen ( " givenwidth " , t . width or 0 )
679 texsetdimen ( " givenheight " , t . height or 0 )
680 texsetdimen ( " givendepth " , t . depth or 0 )
681 end ,
682}
683
684local function stripglue ( list )
685 local done = false
686 local first = list
687 while first do
688 local id = getid ( first )
689 if id = = glue_code or id = = penalty_code then
690 first = getnext ( first )
691 else
692 break
693 end
694 end
695 if first and first ~ = list then
696
697 setsplit ( getprev ( first ) , first )
698 flush_list ( list )
699 list = first
700 done = true
701 end
702 if list then
703 local tail = findtail ( list )
704 local last = tail
705 while last do
706 local id = getid ( last )
707 if id = = glue_code or id = = penalty_code then
708 last = getprev ( last )
709 else
710 break
711 end
712 end
713 if last ~ = tail then
714
715 flush_list ( getnext ( last ) )
716 setnext ( last )
717 done = true
718 end
719 end
720 return list , done
721end
722
723local function limitate ( t )
724 local text = t . text
725 if text then
726 text = tonut ( text )
727 else
728 return
729 end
730 local sentinel = t . sentinel
731 if sentinel then
732 sentinel = tonut ( sentinel )
733 local s = getlist ( sentinel )
734 setlist ( sentinel )
735 free ( sentinel )
736 sentinel = s
737 else
738 return tonode ( text )
739 end
740 local width = getwidth ( text )
741 local list = getlist ( text )
742 local done = false
743 if t . strip then
744 list , done = stripglue ( list )
745 if not list then
746 setlist ( text )
747 setwidth ( text , 0 )
748 return text
749 elseif done then
750 width = getdimensions ( list )
751 setlist ( text , list )
752 end
753 end
754 local left = t . left or 0
755 local right = t . right or 0
756 if left + right < width then
757 local last = nil
758 local first = nil
759 local maxleft = left
760 local maxright = right
761 local swidth = getwidth ( sentinel )
762 if maxright > 0 then
763 maxleft = maxleft - swidth / 2
764 maxright = maxright - swidth / 2
765 else
766 maxleft = maxleft - swidth
767 end
768 for n in traverse_id ( glue_code , list ) do
769 local width = getdimensions ( list , n )
770 if width > maxleft then
771 if not last then
772 last = n
773 end
774 break
775 else
776 last = n
777 end
778 end
779 if last and maxright > 0 then
780 for n in traverse_id ( glue_code , last ) do
781 local width = getdimensions ( n )
782 if width < maxright then
783 first = n
784 break
785 else
786 first = n
787 end
788 end
789 end
790 if last then
791 local rest = getnext ( last )
792 if rest then
793 local tail = findtail ( sentinel )
794 if first and getid ( first ) = = glue_code and getid ( tail ) = = glue_code then
795 setwidth ( first , 0 )
796 end
797 if last and getid ( last ) = = glue_code and getid ( sentinel ) = = glue_code then
798 setwidth ( last , 0 )
799 end
800 if first and first ~ = last then
801 local prev = getprev ( first )
802 if prev then
803 setnext ( prev )
804 end
805 setlink ( tail , first )
806 end
807 setlink ( last , sentinel )
808 setprev ( rest )
809 flush_list ( rest )
810 end
811 end
812 end
813 setlist ( text )
814 free ( text )
815 return tonode ( list )
816end
817
818implement {
819 name = " limitated " ,
820 public = true ,
821 protected = true ,
822 arguments = {
823 {
824 { " left " , " dimension " } ,
825 { " right " , " dimension " } ,
826 { " text " , " hbox " } ,
827 { " sentinel " , " hbox " } ,
828 { " strip " , " boolean " } ,
829 }
830 } ,
831 actions = function ( t )
832 context . dontleavehmode ( )
833 context ( limitate ( t ) )
834 end ,
835}
836
837if CONTEXTLMTXMODE > 0 then
838
839 implement {
840 name = " widthuptohere " ,
841 public = true ,
842 protected = true ,
843 value = true ,
844 actions = function ( )
845 local n = texgetnest ( )
846 local w = 0
847 if n . mode = = hmode_code then
848 local h = hpack ( getnext ( tonut ( n . head ) ) )
849 w = getwidth ( h )
850 setlist ( h )
851 free ( h )
852 end
853 return tokens . values . dimension , w
854 end ,
855 }
856
857end
858
859implement {
860 name = " doifelseindented " ,
861 public = true ,
862 protected = true ,
863 actions = function ( )
864 local n = texgetnest ( )
865 local b = false
866 if n . mode = = hmode_code then
867 n = tonut ( n . head )
868 while n do
869 n = getnext ( n )
870 if n then
871 local id = getid ( n )
872 if id = = hlist_code then
873 if getsubtype ( n ) = = indent_code then
874 b = getwidth ( n ) > 0
875 break
876 end
877 elseif id ~ = localpar_code then
878 break
879 end
880 end
881 end
882 end
883 commands . doifelse ( b )
884 end ,
885}
886
887implement {
888 name = " noflinesinbox " ,
889 public = true ,
890 protected = false ,
891 arguments = " integer " ,
892 actions = function ( n )
893 local c = 0
894 local b = getbox ( n )
895 if b then
896 b = getlist ( b )
897 if b then
898 for n , id in traverse ( b ) do
899 if id = = hlist_code or id = = vlist_code then
900 c = c + 1
901 end
902 end
903 end
904 end
905 context ( c )
906 end ,
907}
908 |