1if not modules then modules = { } end modules ['supp-box'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to supp-box.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 report_hyphenation = logs.reporter("languages","hyphenation")
11
12local tonumber, next, type = tonumber, next, type
13
14local lpegmatch = lpeg.match
15
16local tex = tex
17local context = context
18local nodes = nodes
19
20local implement = interfaces.implement
21
22local ctx_doifelse = commands.doifelse
23
24local nodecodes = nodes.nodecodes
25
26local disc_code <const> = nodecodes.disc
27local hlist_code <const> = nodecodes.hlist
28local vlist_code <const> = nodecodes.vlist
29local glue_code <const> = nodecodes.glue
30local penalty_code <const> = nodecodes.penalty
31local glyph_code <const> = nodecodes.glyph
32local par_code <const> = nodecodes.par
33local rule_code <const> = nodecodes.rule
34
35local indentlist_code <const> = nodes.listcodes.indent
36local line_code <const> = nodes.listcodes.line
37local balanceslot_code <const> = nodes.listcodes.balanceslot
38
39
40local spacing_code <const> = nodes.rulecodes.spacing
41
42local topskip_code <const> = nodes.gluecodes.topskip
43local indentskip_code <const> = nodes.gluecodes.indentskip
44local userskip_code <const> = nodes.gluecodes.userskip
45local baselineskip_code <const> = nodes.gluecodes.baselineskip
46
47local hmode_code <const> = tex.modelevels.horizontal
48
49local nuts = nodes.nuts
50local tonut = nuts.tonut
51local tonode = nuts.tonode
52
53local getnext = nuts.getnext
54local getprev = nuts.getprev
55local getboth = nuts.getboth
56local getdisc = nuts.getdisc
57local getid = nuts.getid
58local getsubtype = nuts.getsubtype
59local getidsubtype = nuts.getidsubtype
60local getlist = nuts.getlist
61local getattribute = nuts.getattribute
62local getbox = nuts.getbox
63local getdirection = nuts.getdirection
64local getwidth = nuts.getwidth
65local getheight = nuts.getheight
66local getdepth = nuts.getdepth
67local getwhd = nuts.getwhd
68local takebox = nuts.takebox
69
70local setlink = nuts.setlink
71local setboth = nuts.setboth
72local setnext = nuts.setnext
73local setprev = nuts.setprev
74local setbox = nuts.setbox
75local setlist = nuts.setlist
76local setdisc = nuts.setdisc
77local setwidth = nuts.setwidth
78local setheight = nuts.setheight
79local setdepth = nuts.setdepth
80local setshift = nuts.setshift
81local setsplit = nuts.setsplit
82local setattrlist = nuts.setattrlist
83local setwhd = nuts.setwhd
84local setglue = nuts.setglue
85local setsubtype = nuts.setsubtype
86
87local flushnode = nuts.flushnode
88local flushlist = nuts.flushlist
89local copynode = nuts.copy
90local copylist = nuts.copylist
91local findtail = nuts.tail
92local getdimensions = nuts.dimensions
93local getwidth = nuts.getwidth
94local hpack = nuts.hpack
95local vpack = nuts.vpack
96local traverseid = nuts.traverseid
97
98local free = nuts.free
99local findtail = nuts.tail
100local reverse = nuts.reverse
101local effectiveglue = nuts.effectiveglue
102
103local insertbefore = nuts.insertbefore
104local replacenode = nuts.replace
105local insertafter = nuts.insertafter
106
107local nextdisc = nuts.traversers.disc
108local nextdir = nuts.traversers.dir
109local nexthlist = nuts.traversers.hlist
110
111local countnodes = nuts.count
112
113
114local listtoutf = nodes.listtoutf
115
116local nodepool = nuts.pool
117local new_penalty = nodepool.penalty
118local new_spacing = nodepool.spacingrule
119local new_hlist = nodepool.hlist
120local new_glue = nodepool.glue
121
122local setlistcolor = nodes.tracers.colors.setlist
123
124local texget = tex.get
125local texgetbox = tex.getbox
126local texisdimen = tex.isdimen
127local texsetdimen = tex.setdimen
128local texgetnest = tex.getnest
129
130local values = tokens.values
131local dimension_value <const> = values.dimension
132local cardinal_value <const> = values.cardinal
133local direct_value <const> = values.direct
134local conditional_value <const> = values.conditional
135local boolean_value <const> = values.boolean
136
137local d_lastnaturalboxwd <const> = texisdimen("lastnaturalboxwd")
138local d_lastnaturalboxht <const> = texisdimen("lastnaturalboxht")
139local d_lastnaturalboxdp <const> = texisdimen("lastnaturalboxdp")
140
141local d_givenwidth <const> = texisdimen("givenwidth")
142local d_givenheight <const> = texisdimen("givenheight")
143local d_givendepth <const> = texisdimen("givendepth")
144
145local function hyphenatedlist(head,usecolor)
146 local current = head and tonut(head)
147 while current do
148 local id = getid(current)
149 local prev, next = getboth(current)
150 if id == disc_code then
151 local pre, post, replace = getdisc(current)
152 if not usecolor then
153
154 elseif pre and post then
155 setlistcolor(pre,"darkmagenta")
156 setlistcolor(post,"darkcyan")
157 elseif pre then
158 setlistcolor(pre,"darkyellow")
159 elseif post then
160 setlistcolor(post,"darkyellow")
161 end
162 if replace then
163 flushlist(replace)
164 end
165 setdisc(current)
166 if pre then
167 setlink(prev,new_penalty(10000),pre)
168 setlink(findtail(pre),current)
169 end
170 if post then
171 setlink(current,new_penalty(10000),post)
172 setlink(findtail(post),next)
173 end
174 elseif id == vlist_code or id == hlist_code then
175 hyphenatedlist(getlist(current))
176 end
177 current = next
178 end
179end
180
181implement {
182 name = "hyphenatedlist",
183 arguments = { "integer", "boolean" },
184 actions = function(n,color)
185 local b = texgetbox(n)
186 if b then
187 hyphenatedlist(b.list,color)
188 end
189 end
190}
191
192local function checkedlist(list)
193 if type(list) == "number" then
194 return getlist(getbox(tonut(list)))
195 else
196 return tonut(list)
197 end
198end
199
200implement {
201 name = "showhyphenatedinlist",
202 arguments = "integer",
203 actions = function(n)
204
205 local l = languages.hyphenators.handler(checkedlist(n))
206 report_hyphenation("show: %s",listtoutf(l,false,true))
207 end
208}
209
210do
211
212 local function applytochars(current,doaction,noaction,nested)
213 while current do
214 local id = getid(current)
215 if nested and (id == hlist_code or id == vlist_code) then
216 context.beginhbox()
217 applytochars(getlist(current),doaction,noaction,nested)
218 context.endhbox()
219 elseif id ~= glyph_code then
220 noaction(tonode(copynode(current)))
221 else
222 doaction(tonode(copynode(current)))
223 end
224 current = getnext(current)
225 end
226 end
227
228 local function applytowords(current,doaction,noaction,nested)
229 local start
230 while current do
231 local id = getid(current)
232 if id == glue_code then
233 if start then
234 doaction(tonode(copylist(start,current)))
235 start = nil
236 end
237 noaction(tonode(copynode(current)))
238 elseif nested and (id == hlist_code or id == vlist_code) then
239 context.beginhbox()
240 applytowords(getlist(current),doaction,noaction,nested)
241 context.egroup()
242 elseif not start then
243 start = current
244 end
245 current = getnext(current)
246 end
247 if start then
248 doaction(tonode(copylist(start)))
249 end
250 end
251
252 local methods = {
253 char = applytochars,
254 characters = applytochars,
255 word = applytowords,
256 words = applytowords,
257 }
258
259 implement {
260 name = "applytobox",
261 arguments = {
262 {
263 { "box", "integer" },
264 { "command" },
265 { "method" },
266 { "nested", "boolean" },
267 }
268 },
269 actions = function(specification)
270 local list = checkedlist(specification.box)
271 local action = methods[specification.method or "char"]
272 if list and action then
273 action(list,context[specification.command or "ruledhbox"],context,specification.nested)
274 end
275 end
276 }
277
278 local split_char = lpeg.Ct(lpeg.C(1)^0)
279 local split_word = lpeg.tsplitat(lpeg.patterns.space)
280 local split_line = lpeg.tsplitat(lpeg.patterns.eol)
281
282 local function processsplit(specification)
283 local str = specification.data or ""
284 local command = specification.command or "ruledhbox"
285 local method = specification.method or "word"
286 local spaced = specification.spaced
287 if command then
288 command = context[command]
289 end
290 if method == "char" or method == "character" then
291 if spaced then
292 spaced = context.space
293 end
294 local words = lpegmatch(split_char,str)
295 for i=1,#words do
296 local word = words[i]
297 if word == " " then
298 if spaced then
299 spaced()
300 end
301 elseif command then
302 command(word)
303 else
304 context(word)
305 end
306 end
307 elseif method == "word" then
308 if spaced then
309 spaced = context.space
310 end
311 local words = lpegmatch(split_word,str)
312 for i=1,#words do
313 local word = words[i]
314 if spaced and i > 1 then
315 spaced()
316 end
317 if command then
318 command(word)
319 else
320 context(word)
321 end
322 end
323 elseif method == "line" then
324 if spaced then
325 spaced = context.par
326 end
327 local words = lpegmatch(split_line,str)
328 for i=1,#words do
329 local word = words[i]
330 if spaced and i > 1 then
331 spaced()
332 end
333 if command then
334 command(word)
335 else
336 context(word)
337 end
338 end
339 else
340 context(str)
341 end
342 end
343
344 implement {
345 name = "processsplit",
346 actions = processsplit,
347 arguments = {
348 {
349 { "data" },
350 { "command" },
351 { "method" },
352 { "spaced", "boolean" },
353 }
354 }
355 }
356
357end
358
359do
360
361 local a_vboxtohboxseparator <const> = attributes.private("vboxtohboxseparator")
362
363 implement {
364 name = "vboxlisttohbox",
365 arguments = { "integer", "integer", "dimension" },
366 actions = function(original,target,inbetween)
367 local current = getlist(getbox(original))
368 local head = nil
369 local tail = nil
370 while current do
371 local id = getid(current)
372 local next = getnext(current)
373 if id == hlist_code then
374 local list = getlist(current)
375 if head then
376 if inbetween > 0 then
377 local n = new_glue(0,0,inbetween)
378 setlink(tail,n)
379 tail = n
380 end
381 setlink(tail,list)
382 else
383 head = list
384 end
385 tail = findtail(list)
386
387 if getid(tail) == hlist_code and getattribute(tail,a_vboxtohboxseparator) == 1 then
388 local temp = tail
389 local prev = getprev(tail)
390 if next then
391 local list = getlist(tail)
392 setlink(prev,list)
393 setlist(tail)
394 tail = findtail(list)
395 else
396 tail = prev
397 end
398 flushnode(temp)
399 end
400
401 setnext(tail)
402 setlist(current)
403 end
404 current = next
405 end
406 local result = new_hlist()
407 setlist(result,head)
408 setbox(target,result)
409
410 end
411 }
412
413 implement {
414 name = "hboxtovbox",
415 arguments = "integer",
416 actions = function(n)
417 local b = getbox(n)
418 local factor = texget("baselineskip",false) / texget("hsize")
419 setdepth(b,0)
420 setheight(b,getwidth(b) * factor)
421 end
422 }
423
424end
425
426implement {
427 name = "boxtostring",
428 arguments = "integer",
429 public = true,
430 actions = function(n)
431 local b = texgetbox(n)
432 local l = b and b.list
433 if l then
434
435 local s = nodes.toutf(l)
436 if s then
437 context.verbatim(s)
438 end
439 end
440 end
441}
442
443do
444
445
446
447 local function getnaturaldimensions(n)
448 local w = 0
449 local h = 0
450 local d = 0
451 local l = getlist(getbox(n))
452 if l then
453 w, h, d = getdimensions(l)
454 end
455 texsetdimen(d_lastnaturalboxwd,w)
456 texsetdimen(d_lastnaturalboxht,h)
457 texsetdimen(d_lastnaturalboxdp,d)
458 return w, h, d
459 end
460
461 local function setboxtonaturalwd(n)
462 local old = takebox(n)
463 local new = hpack(getlist(old))
464 setlist(old,nil)
465 flushnode(old)
466 setbox(n,new)
467 end
468
469 implement {
470 name = "getnaturaldimensions",
471 arguments = "integer",
472 public = true,
473 protected = true,
474 untraced = true,
475 actions = getnaturaldimensions
476 }
477
478 implement {
479 name = "naturalwd",
480 arguments = "integer",
481 usage = "value",
482 public = true,
483 protected = true,
484 actions = function(n)
485 return dimension_value, (getnaturaldimensions(n))
486 end
487 }
488
489 implement {
490 name = "getnaturalwd",
491 arguments = "integer",
492 usage = "value",
493 public = true,
494 protected = true,
495 actions = function(n)
496 local l = getlist(getbox(n))
497 return dimension_value, l and getdimensions(l) or 0
498 end
499 }
500
501 implement {
502 name = "setnaturalwd",
503 arguments = "integer",
504 public = true,
505 protected = true,
506 untraced = true,
507 actions = setboxtonaturalwd
508 }
509
510 nodes.setboxtonaturalwd = setboxtonaturalwd
511
512end
513
514do
515
516 local lefttoright_code <const> = tex.directioncodes.lefttoright
517 local righttoleft_code <const> = tex.directioncodes.righttoleft
518
519 local function firstdirinbox(n)
520 local b = getbox(n)
521 if b then
522 local l = getlist(b)
523 if l then
524 for d in nextdir, l do
525 return getdirection(d)
526 end
527 for h in nexthlist, l do
528 return getdirection(h)
529 end
530 end
531 end
532 return lefttoright_code
533 end
534
535 nodes.firstdirinbox = firstdirinbox
536
537 implement {
538 name = "doifelserighttoleftinbox",
539 arguments = "integer",
540 actions = function(n)
541 ctx_doifelse(firstdirinbox(n) == righttoleft_code)
542 end
543 }
544
545end
546
547
548
549do
550
551
552
553
554
555
556
557
558
559 local boxes = { }
560 nodes.boxes = boxes
561 local cache = table.setmetatableindex("table")
562 local report = logs.reporter("boxes","cache")
563 local trace = false
564
565 trackers.register("nodes.boxes",function(v) trace = v end)
566
567 function boxes.save(category,name,b)
568 name = tonumber(name) or name
569 local b = takebox(b)
570 if trace then
571 report("category %a, name %a, %s (%s)",category,name,"save",b and "content" or "empty")
572 end
573 if name == "+" then
574 name = #cache[category] + 1
575 end
576 cache[category][name] = b or false
577 end
578
579 function boxes.savenode(category,name,n)
580 name = tonumber(name) or name
581 if trace then
582 report("category %a, name %a, %s (%s)",category,name,"save",n and "content" or "empty")
583 end
584 cache[category][name] = tonut(n) or false
585 end
586
587 function boxes.found(category,name)
588 name = tonumber(name) or name
589 return cache[category][name] and true or false
590 end
591
592 function boxes.count(category)
593 return #cache[category]
594 end
595
596 function boxes.direct(category,name,copy)
597 name = tonumber(name) or name
598 local c = cache[category]
599 local b = c[name]
600
601
602
603
604
605 if not b then
606
607 elseif copy then
608 b = copylist(b)
609 else
610 c[name] = false
611 end
612 if trace then
613 report("category %a, name %a, %s (%s)",category,name,"direct",b and "content" or "empty")
614 end
615 if b then
616 return tonode(b)
617 end
618 end
619
620 function boxes.restore(category,name,box,copy)
621 name = tonumber(name) or name
622 local c = cache[category]
623 local b = takebox(box)
624 if b then
625 flushlist(b)
626 end
627 local b = c[name]
628 if not b then
629
630 elseif copy then
631 b = copylist(b)
632 else
633 c[name] = false
634 end
635 if trace then
636 report("category %a, name %a, %s (%s)",category,name,"restore",b and "content" or "empty")
637 end
638 setbox(box,b or nil)
639 end
640
641 function boxes.prune(category)
642
643 local c = cache[category]
644 local t = { }
645 local n = 0
646 for i=1,#c do
647 local ci = c[i]
648 if ci then
649 n = n + 1 ; t[n] = ci
650 end
651 end
652 cache[category] = t
653 end
654
655 local function dimensions(category,name)
656 name = tonumber(name) or name
657 local b = cache[category][name]
658 if b then
659 return getwhd(b)
660 else
661 return 0, 0, 0
662 end
663 end
664
665 boxes.dimensions = dimensions
666
667 function boxes.reset(category,name)
668 name = tonumber(name) or name
669 local c = cache[category]
670 if name and name ~= "" then
671 local b = c[name]
672 if b then
673 flushlist(b)
674 c[name] = false
675 end
676 if trace then
677 report("category %a, name %a, reset",category,name)
678 end
679 else
680 for k, b in next, c do
681 if b then
682 flushlist(b)
683 end
684 end
685 cache[category] = { }
686 if trace then
687 report("category %a, reset",category)
688 end
689 end
690 end
691
692 function boxes.dispose(category)
693 boxes.reset(category)
694 cache[category] = nil
695 end
696
697 implement {
698 name = "putboxincache",
699 public = true,
700 protected = true,
701 arguments = { "string", "string", "integer" },
702 actions = boxes.save,
703 }
704
705 implement {
706 name = "getboxfromcache",
707 public = true,
708 protected = true,
709 arguments = { "string", "string", "integer" },
710 actions = boxes.restore,
711 }
712
713 implement {
714 name = "directboxfromcache",
715 public = true,
716 protected = true,
717 arguments = "2 strings",
718 actions = { boxes.direct, context },
719
720 }
721
722 implement {
723 name = "directcopyboxfromcache",
724 public = true,
725 protected = true,
726 arguments = { "string", "string", true },
727 actions = { boxes.direct, context },
728
729 }
730
731 implement {
732 name = "copyboxfromcache",
733 public = true,
734 protected = true,
735 arguments = { "string", "string", "integer", true },
736 actions = boxes.restore,
737 }
738
739 implement {
740 name = "doifelseboxincache",
741 public = true,
742 protected = true,
743 arguments = "2 strings",
744 actions = { boxes.found, ctx_doifelse },
745 }
746
747 implement {
748 name = "resetboxesincache",
749 public = true,
750 protected = true,
751 arguments = "string",
752 actions = boxes.reset,
753 }
754
755 implement {
756 name = "pruneboxesincache",
757 public = true,
758 protected = true,
759 arguments = "string",
760 actions = boxes.prune,
761 }
762
763 implement {
764 name = "disposeboxesincache",
765 public = true,
766 protected = true,
767 arguments = "string",
768 actions = boxes.dispose,
769 }
770
771
772
773 implement {
774 name = "getboxcountfromcache",
775 public = true,
776 protected = true,
777 usage = "value",
778 arguments = "string",
779 actions = function(category)
780 return cardinal_value, #cache[category]
781 end,
782 }
783
784 implement {
785 name = "getboxwdfromcache",
786 public = true,
787 protected = true,
788 usage = "value",
789 arguments = "2 strings",
790 actions = function(category,name)
791 local w, h, d = dimensions(category,name)
792 return dimension_value, w
793 end,
794 }
795
796 implement {
797 name = "getboxhtfromcache",
798 arguments = "2 strings",
799 public = true,
800 protected = true,
801 usage = "value",
802 actions = function(category,name)
803 local w, h, d = dimensions(category,name)
804 return dimension_value, h
805 end,
806 }
807
808 implement {
809 name = "getboxdpfromcache",
810 arguments = "2 strings",
811 public = true,
812 protected = true,
813 usage = "value",
814 actions = function(category,name)
815 local w, h, d = dimensions(category,name)
816 return dimension_value, d
817 end,
818 }
819
820 implement {
821 name = "getboxhtdpfromcache",
822 arguments = "2 strings",
823 public = true,
824 protected = true,
825 usage = "value",
826 actions = function(category,name)
827 local w, h, d = dimensions(category,name)
828 return dimension_value, h + d
829 end,
830 }
831
832 implement {
833 name = "putnextboxincache",
834 public = true,
835 protected = true,
836 arguments = { "string", "string", "box" },
837 actions = function(category,name,b)
838 name = tonumber(name) or name
839 b = tonut(b)
840 if trace then
841 report("category %a, name %a, %s (%s)",category,name,"save",b and "content" or "empty")
842 end
843 cache[category][name] = b or false
844 end
845 }
846
847end
848
849
850
851
852
853 local head = tex.lists.pagehead
854
855
856
857
858
859implement {
860 name = "lastlinewidth",
861 usage = "value",
862 public = true,
863 protected = true,
864 actions = function()
865 local n = texgetnest()
866 local l = n and getlist(tonut(n.tail))
867
868 return dimension_value, l and getdimensions(l) or 0
869 end
870}
871
872
873
874
875
876
877
878
879
880implement { name = "vpackbox", arguments = "integer", actions = function(n) setbox(n,(vpack(takebox(n)))) end }
881implement { name = "hpackbox", arguments = "integer", actions = function(n) setbox(n,(hpack(takebox(n)))) end }
882
883implement { name = "vpackedbox", arguments = "integer", actions = function(n) context(vpack(takebox(n))) end }
884implement { name = "hpackedbox", arguments = "integer", actions = function(n) context(hpack(takebox(n))) end }
885
886implement {
887 name = "scangivendimensions",
888 public = true,
889 protected = true,
890 arguments = {
891 {
892 { "width", "dimension" },
893 { "height", "dimension" },
894 { "depth", "dimension" },
895 },
896 },
897 actions = function(t)
898 texsetdimen(d_givenwidth, t.width or 0)
899 texsetdimen(d_givenheight,t.height or 0)
900 texsetdimen(d_givendepth, t.depth or 0)
901 end,
902}
903
904local function stripglue(list)
905 local done = false
906 local first = list
907 while first do
908 local id = getid(first)
909 if id == glue_code or id == penalty_code then
910 first = getnext(first)
911 else
912 break
913 end
914 end
915 if first and first ~= list then
916
917 setsplit(getprev(first),first)
918 flushlist(list)
919 list = first
920 done = true
921 end
922 if list then
923 local tail = findtail(list)
924 local last = tail
925 while last do
926 local id = getid(last)
927 if id == glue_code or id == penalty_code then
928 last = getprev(last)
929 else
930 break
931 end
932 end
933 if last ~= tail then
934
935 flushlist(getnext(last))
936 setnext(last)
937 done = true
938 end
939 end
940 return list, done
941end
942
943local function limitate(t)
944 local text = t.text
945 if text then
946 text = tonut(text)
947 else
948 return
949 end
950 local sentinel = t.sentinel
951 if sentinel then
952 sentinel = tonut(sentinel)
953 local s = getlist(sentinel)
954 setlist(sentinel)
955 free(sentinel)
956 sentinel = s
957 else
958 return tonode(text)
959 end
960 local width = getwidth(text)
961 local list = getlist(text)
962 local done = false
963 if t.strip then
964 list, done = stripglue(list)
965 if not list then
966 setlist(text)
967 setwidth(text,0)
968 return text
969 elseif done then
970 width = getdimensions(list)
971 setlist(text,list)
972 end
973 end
974 local left = t.left or 0
975 local right = t.right or 0
976 local total = left + right
977 if total < width then
978 local last = nil
979 local first = nil
980 local maxleft = left
981 local maxright = right
982 local swidth = getwidth(sentinel)
983 if maxright > 0 then
984 maxleft = maxleft - swidth/2
985 maxright = maxright - swidth/2
986 else
987 maxleft = maxleft - swidth
988 end
989 for n in traverseid(glue_code,list) do
990 local width = getdimensions(list,n)
991 if width > maxleft then
992 if not last then
993 last = n
994 end
995 break
996 else
997 last = n
998 end
999 end
1000 if last and maxright > 0 then
1001 for n in traverseid(glue_code,last) do
1002 local width = getdimensions(n)
1003 if width < maxright then
1004 first = n
1005 break
1006 else
1007 first = n
1008 end
1009 end
1010 end
1011 if last then
1012 local rest = getnext(last)
1013 if rest then
1014 local tail = findtail(sentinel)
1015 if first and getid(first) == glue_code and getid(tail) == glue_code then
1016 setwidth(first,0)
1017 end
1018 if last and getid(last) == glue_code and getid(sentinel) == glue_code then
1019 setwidth(last,0)
1020 end
1021 if first and first ~= last then
1022 local prev = getprev(first)
1023 if prev then
1024 setnext(prev)
1025 end
1026 setlink(tail,first)
1027 end
1028 setlink(last,sentinel)
1029 setprev(rest)
1030 flushlist(rest)
1031 end
1032 end
1033 end
1034 setlist(text)
1035 free(text)
1036
1037 if t.freeze then
1038 local l = hpack(list,total,"exactly")
1039 for n in traverseid(glue_code,list) do
1040 setglue(n,(effectiveglue(n,l)))
1041 end
1042 setlist(l)
1043 flushnode(l)
1044 end
1045
1046 return tonode(list)
1047end
1048
1049implement {
1050 name = "limitated",
1051 public = true,
1052 protected = true,
1053 arguments = {
1054 {
1055 { "left", "dimension" },
1056 { "right", "dimension" },
1057 { "text", "hbox" },
1058 { "sentinel", "hbox" },
1059 { "strip", "boolean" },
1060 { "freeze", "boolean" },
1061 }
1062 },
1063 actions = function(t)
1064 context.dontleavehmode()
1065 context(limitate(t))
1066 end,
1067}
1068
1069
1070
1071do
1072
1073 local naturalhsize = nuts.naturalhsize
1074
1075 implement {
1076 name = "widthuptohere",
1077 public = true,
1078 usage = "value",
1079 actions = function()
1080 local n = texgetnest()
1081 return dimension_value, n.mode == hmode_code and naturalhsize(getnext(tonut(n.head))) or 0
1082 end,
1083 }
1084
1085end
1086
1087implement {
1088 name = "doifelseindented",
1089 public = true,
1090 protected = true,
1091 actions = function()
1092 local n = texgetnest()
1093 local b = false
1094
1095 if n.mode == hmode_code then
1096 n = tonut(n.head)
1097 while n do
1098 n = getnext(n)
1099 if n then
1100 local id, subtype = getidsubtype(n)
1101 if id == glue_code then
1102 if subtype == indentskip_code then
1103 b = getwidth(n) > 0
1104 break
1105 end
1106 elseif id == hlist_code then
1107 if subtype == indentlist_code then
1108 b = getwidth(n) > 0
1109 end
1110 break
1111 elseif id == par_code then
1112
1113 else
1114 break
1115 end
1116 end
1117 end
1118 end
1119 ctx_doifelse(b)
1120 end,
1121}
1122
1123implement {
1124 name = "noflinesinbox",
1125 public = true,
1126 usage = "value",
1127 arguments = "integer",
1128 actions = function(n)
1129 local c = 0
1130 local b = getbox(n)
1131 if b then
1132 b = getlist(b)
1133 if b then
1134 c = countnodes(hlist_code,b) + countnodes(vlist_code,b)
1135 end
1136 end
1137 return cardinal_value, c
1138 end,
1139}
1140
1141implement {
1142 name = "gluetolinesinbox",
1143 public = true,
1144 protected = false,
1145 arguments = { "integer", "dimension", "dimension", "dimension" },
1146 actions = function(n,wd,ht,dp)
1147
1148 local box = getbox(n)
1149 if box then
1150 local head = getlist(box)
1151 local current = head
1152 while current do
1153 local id, subtype = getidsubtype(current)
1154 if id == glue_code and subtype == userskip_code then
1155
1156
1157
1158 local line = new_spacing(wd,ht,dp)
1159
1160 setattrlist(line,current)
1161 head = insertbefore(head,current,new_glue(baselineskip_code))
1162 head, current = replacenode(head,current,line)
1163 head, current = insertafter(head,current,new_glue(baselineskip_code))
1164 end
1165 current = getnext(current)
1166 end
1167 setlist(box,head)
1168 end
1169 end,
1170}
1171
1172implement {
1173 name = "ifgluelineinbox",
1174 public = true,
1175 usage = "condition",
1176 arguments = "integer",
1177 actions = function(n)
1178 local box = getbox(n)
1179 local found = false
1180 if box and getsubtype(box) == balanceslot_code then
1181 local current = getlist(box)
1182 while current do
1183 local id, subtype = getidsubtype(current)
1184
1185 if id == rule_code and subtype == spacing_code then
1186 found = true
1187 break
1188 end
1189 current = getnext(current)
1190 end
1191 end
1192 return boolean_value, found
1193 end,
1194}
1195
1196do
1197
1198 local takebox = tex.takebox
1199
1200 implement {
1201 name = "thebox",
1202 public = true,
1203 arguments = "integer",
1204 actions = function(n)
1205 context(takebox(n))
1206 end
1207 }
1208
1209end
1210
1211
1212
1213implement {
1214 name = "reversevboxcontent",
1215 protected = true,
1216 public = true,
1217 arguments = "integer",
1218 actions = function(n)
1219 local b = getbox(n)
1220 local l = b and getid(b) == vlist_code and getlist(b)
1221 if l and getnext(l) then
1222 setlist(b,reverse(l))
1223 end
1224 end
1225}
1226
1227
1228
1229do
1230
1231 local scaninteger = tokens.scanners.integer
1232 local scanbox = tokens.scanners.box
1233 local scandimension = tokens.scanners.dimension
1234
1235
1236 local removenode = nuts.remove
1237 local getnormalizedline = nuts.getnormalizedline
1238 local getdimensions = nuts.dimensions
1239 local getrangedimensions = nuts.rangedimensions
1240 local getwidth = nuts.getwidth
1241 local getheight = nuts.getheight
1242 local getdepth = nuts.getdepth
1243
1244 local setprop = nuts.setprop
1245 local getprop = nuts.getprop
1246
1247 local line_code <const> = nodes.listcodes.line
1248 local alignment_code <const> = nodes.listcodes.alignment
1249 local unknown_code <const> = nodes.listcodes.unknown
1250
1251
1252
1253 local reporterror = logs.texerrormessage
1254
1255
1256
1257
1258
1259
1260 local function countlines(box)
1261 local prop = getprop(box,"boxlines")
1262 if not prop then
1263 local line = 0
1264 local list = getlist(box)
1265
1266 prop = { }
1267 if list then
1268 for n, subtype in nexthlist, list do
1269 if subtype == line_code or subtype == alignment_code then
1270 line = line + 1
1271 prop[line] = n
1272
1273
1274
1275
1276 end
1277 end
1278 end
1279 setprop(box,"boxlines",prop)
1280 end
1281 return prop
1282 end
1283
1284 local function boxlinecount(what)
1285 local n = scaninteger()
1286 local box = getbox(n)
1287 if what == "value" then
1288 return cardinal_value, box and #countlines(box) or 0
1289 end
1290 end
1291
1292 local function boxlinemaxwhd(what,get)
1293 local n = scaninteger()
1294 local box = getbox(n)
1295 if box and what == "value" then
1296 local prop = getprop(box,"boxlines")
1297 if not prop then
1298 prop = countlines(box)
1299 end
1300 local v = 0
1301 if prop then
1302 for i=1,#prop do
1303 local w = get(prop[i])
1304 if w > v then
1305 v = w
1306 end
1307 end
1308 end
1309 return dimension_value, v
1310 end
1311 end
1312
1313 local function boxlinemaxwd(what) return boxlinemaxwhd(what,getwidth) end
1314 local function boxlinemaxht(what) return boxlinemaxwhd(what,getheight) end
1315 local function boxlinemaxdp(what) return boxlinemaxwhd(what,getdepth) end
1316
1317 local function findline()
1318 local n = scaninteger()
1319 local line = scaninteger()
1320 local box = getbox(n)
1321 if box then
1322 local prop = getprop(box,"boxlines")
1323 if not prop then
1324 prop = countlines(box)
1325 end
1326 local found = prop[line]
1327 if found then
1328 local props = getprop(found,"lineproperties") or { }
1329 if not props then
1330 props = getnormalizedline(found)
1331 props.width, props.height, props.depth = getwhd(found)
1332 setprop(found,"lineproperties",props)
1333 end
1334 return props, line, found, box
1335 end
1336 end
1337 reporterror("no line %i in box %i",line,n)
1338 end
1339
1340 local function findrange()
1341 local n = scaninteger()
1342 local first = scaninteger()
1343 local last = scaninteger()
1344 local box = getbox(n)
1345 if box then
1346 local prop = getprop(box,"boxlines")
1347 if not prop then
1348 prop = countlines(box)
1349 end
1350 if first > 0 and last <= #prop then
1351 for i = first, last do
1352 local found = prop[i]
1353 local props = getprop(found,"lineproperties")
1354 if not props then
1355 props = getnormalizedline(found) or { }
1356 props.width, props.height, props.depth = getwhd(found)
1357 setprop(found,"lineproperties",props)
1358 end
1359 end
1360 return prop, first, last, box
1361 end
1362 end
1363 reporterror("no lines %i - %i in box %i",first,last,n)
1364 end
1365
1366 local function getline(props,line,found,box,value)
1367 local p, n = getboth(found)
1368 local temp = new_hlist()
1369 setsubtype(temp,getsubtype(found))
1370 setwhd(temp,getwhd(found))
1371 if found == getlist(box) then
1372 setlink(temp,n)
1373 setlist(box,temp)
1374 else
1375 setlink(p,temp,n)
1376 end
1377 getprop(box,"boxlines")[line] = temp
1378 setboth(found)
1379 setsubtype(found, unknown_code)
1380 if value then
1381 return direct_value, found
1382 else
1383 context(tonode(found))
1384 end
1385 end
1386
1387 local function copyline(props,line,found,box,value)
1388 found = copynode(found)
1389 setsubtype(found, unknown_code)
1390 if value then
1391 return direct_value, found
1392 else
1393 context(tonode(found))
1394 end
1395 end
1396
1397 local function setline(props,line,found,box)
1398 local p, n = getboth(found)
1399 local temp = scanbox()
1400 if temp then
1401 temp = tonut(temp)
1402 if found == getlist(box) then
1403 setlink(temp,n)
1404 setlist(box,temp)
1405 else
1406 setlink(p,temp,n)
1407 end
1408 flushnode(found)
1409 getprop(box,"boxlines")[line] = temp
1410 end
1411 end
1412
1413 local function naturaldimensions(p,l,found)
1414 if not p.naturalwidth then
1415 p.naturalwidth, p.naturalheight, p.naturaldepth = getdimensions(getlist(found))
1416 end
1417 return p
1418 end
1419
1420 local function rangedimensions(p,f,l,box)
1421 local w, h, d = getrangedimensions(box,p[f],getnext(p[l]),true)
1422 return { width = w, height = h, depth = d }
1423 end
1424
1425 local getters_one = {
1426
1427 ["wd"] = function(p,l,found) return dimension_value, p.width end,
1428 ["ht"] = function(p,l,found) return dimension_value, p.height end,
1429 ["dp"] = function(p,l,found) return dimension_value, p.depth end,
1430 ["ls"] = function(p,l,found) return dimension_value, p.leftskip end,
1431 ["rs"] = function(p,l,found) return dimension_value, p.rightskip end,
1432 ["lh"] = function(p,l,found) return dimension_value, p.lefthangskip end,
1433 ["rh"] = function(p,l,found) return dimension_value, p.righthangskip end,
1434 ["lp"] = function(p,l,found) return dimension_value, p.parfillleftskip end,
1435 ["rp"] = function(p,l,found) return dimension_value, p.parfillrightskip end,
1436 ["il"] = function(p,l,found) return dimension_value, p.parinitleftskip end,
1437 ["ir"] = function(p,l,found) return dimension_value, p.parinitrightskip end,
1438 ["in"] = function(p,l,found) return dimension_value, p.indent end,
1439
1440 ["nw"] = function(p,l,found) return dimension_value, naturaldimensions(p,l,found).naturalwidth end,
1441 ["nh"] = function(p,l,found) return dimension_value, naturaldimensions(p,l,found).naturalheigth end,
1442 ["nd"] = function(p,l,found) return dimension_value, naturaldimensions(p,l,found).naturaldepth end,
1443
1444 ["get"] = function(p,l,found,box) return getline(p,l,found,box,true) end,
1445 }
1446
1447 local getters_two = {
1448 ["wd"] = function(p,f,l,box) return dimension_value, rangedimensions(p,f,l,box).width end,
1449 ["ht"] = function(p,f,l,box) return dimension_value, rangedimensions(p,f,l,box).height end,
1450 ["dp"] = function(p,f,l,box) return dimension_value, rangedimensions(p,f,l,box).depth end,
1451 }
1452
1453 local setters_one = {
1454 ["wd"] = function(p,l,found) return setwidth (found,scandimension(false,false,true)) end,
1455 ["ht"] = function(p,l,found) return setheight(found,scandimension(false,false,true)) end,
1456 ["dp"] = function(p,l,found) return setdepth (found,scandimension(false,false,true)) end,
1457 ["set"] = setline,
1458 ["get"] = getline,
1459 ["copy"] = copyline,
1460 }
1461
1462 local function boxline(name,what)
1463 local props, line, found, box = findline()
1464 if not found then
1465
1466 elseif what == "value" then
1467 local getter = getters_one[name]
1468 if getter then
1469 return getter(props,line,found,box)
1470 end
1471 else
1472 local setter = setters_one[name]
1473 if setter then
1474 return setter(props,line,found,box)
1475 end
1476 end
1477 end
1478
1479
1480 local function boxrange(name,what)
1481 local prop, first, last, box = findrange()
1482 if not prop then
1483
1484 elseif what == "value" then
1485 local getter = getters_two[name]
1486 if getter then
1487 return getter(prop,first,last,box)
1488 end
1489 else
1490 local setter = setters_two[name]
1491 if setter then
1492 return setter(prop,first,last,box)
1493 end
1494 end
1495 end
1496
1497 local function define_one(name,action)
1498 implement {
1499 name = name,
1500 public = true,
1501 usage = "value",
1502 actions = function(what) return boxline(action,what) end,
1503 }
1504 end
1505
1506 local function define_two(name,action)
1507 implement {
1508 name = name,
1509 public = true,
1510 usage = "value",
1511 actions = function(what) return boxrange(action,what) end,
1512 }
1513 end
1514
1515 implement {
1516 name = "boxlines",
1517 public = true,
1518 usage = "value",
1519 actions = boxlinecount,
1520 }
1521
1522 implement { name = "boxlinemaxwd", public = true, usage = "value", actions = boxlinemaxwd }
1523 implement { name = "boxlinemaxht", public = true, usage = "value", actions = boxlinemaxht }
1524 implement { name = "boxlinemaxdp", public = true, usage = "value", actions = boxlinemaxdp }
1525
1526
1527
1528 define_one("boxline", "get")
1529 define_one("setboxline", "set")
1530 define_one("copyboxline", "copy")
1531 define_one("boxlineht", "ht")
1532 define_one("boxlinedp", "dp")
1533 define_one("boxlinewd", "wd")
1534 define_one("boxlinels", "ls")
1535 define_one("boxliners", "rs")
1536 define_one("boxlinelh", "lh")
1537 define_one("boxlinerh", "rh")
1538 define_one("boxlinelp", "lp")
1539 define_one("boxlinerp", "rp")
1540 define_one("boxlineil", "il")
1541 define_one("boxlineir", "ir")
1542 define_one("boxlinein", "in")
1543 define_one("boxlinenw", "nw")
1544 define_one("boxlinenh", "nh")
1545 define_one("boxlinend", "nd")
1546
1547 define_two("boxrangewd", "wd")
1548 define_two("boxrangeht", "ht")
1549 define_two("boxrangedp", "dp")
1550
1551end
1552
1553do
1554
1555 local getbox = tex.getbox
1556 local setfield = nodes.setfield
1557 local getfield = nodes.getfield
1558 local flush = nodes.flushnode
1559 local copynode = nodes.copy
1560
1561 local function get(n,field,copy)
1562 local b = getbox(n)
1563 if b then
1564 local p = getfield(b,field)
1565 if copy then
1566 p = copynode(p)
1567 else
1568 setfield(b,field)
1569 end
1570 context(p)
1571 end
1572 end
1573
1574 local function set(n,l,field)
1575 local b = getbox(n)
1576 if b then
1577 setfield(b,field,l)
1578 else
1579 flush(l)
1580 end
1581 end
1582
1583 implement {
1584 name = "prelistbox",
1585 public = true,
1586 usage = "value",
1587 arguments = { "integer", '"pre"' },
1588 actions = get,
1589 }
1590
1591 implement {
1592 name = "postlistbox",
1593 public = true,
1594 usage = "value",
1595 arguments = { "integer", '"post"' },
1596 actions = get,
1597 }
1598
1599 implement {
1600 name = "prelistcopy",
1601 public = true,
1602 usage = "value",
1603 arguments = { "integer", '"pre"', true },
1604 actions = get,
1605 }
1606
1607 implement {
1608 name = "postlistcopy",
1609 public = true,
1610 usage = "value",
1611 arguments = { "integer", '"post"', true },
1612 actions = get,
1613 }
1614
1615 implement {
1616 name = "setprelistbox",
1617 public = true,
1618 usage = "value",
1619 arguments = { "integer", "box", '"pre"' },
1620 actions = set,
1621 }
1622
1623 implement {
1624 name = "setpostlistbox",
1625 public = true,
1626 usage = "value",
1627 arguments = { "integer", "box", '"post"' },
1628 actions = set,
1629 }
1630
1631end
1632
1633do
1634
1635 local function setsplitlisthtdp(n,ht,dp)
1636 local box = getbox(n)
1637 if box then
1638 local head = getlist(box)
1639 if head then
1640 local tail = findtail(head)
1641 local id = getid(head)
1642 if id == glue_code and getsubtype(head) == topskip_code then
1643 head = getnext(head)
1644 id = head and getid(head)
1645 ht = ht - getwidth(head)
1646 end
1647 if id == hlist_code and getsubtype(head) == line_code and getheight(head) < ht then
1648
1649 setheight(head,ht)
1650 end
1651 local id = getid(tail)
1652 if id == glue_code then
1653 tail = getprev(tail)
1654 id = tail and getid(tail)
1655 dp = dp - getwidth(tail)
1656 end
1657 if id == hlist_code and getsubtype(tail) == line_code and getdepth(tail) < dp then
1658 setdepth(dept,dp)
1659
1660 end
1661 end
1662 end
1663 end
1664
1665 implement {
1666 name = "setsplitlisthtdp",
1667 public = true,
1668 protected = true,
1669 arguments = { "integer", "dimension", "dimension" },
1670 actions = setsplitlisthtdp,
1671 }
1672end
1673
1674do
1675
1676 local ctx_validtext = context.core.validtext
1677
1678 implement {
1679 name = "iftext",
1680 public = true,
1681 usage = "condition",
1682 actions = function()
1683
1684 ctx_validtext()
1685 return conditional_value
1686 end
1687 }
1688
1689 implement {
1690 name = "ifboxhaslist",
1691 public = true,
1692 usage = "condition",
1693 arguments = "integer",
1694 actions = function(n)
1695 local b = texgetbox(n)
1696 return boolean_value, b and b.list and true or false
1697 end
1698 }
1699
1700end
1701 |