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 nodecodes = nodes.nodecodes
23
24local disc_code = nodecodes.disc
25local hlist_code = nodecodes.hlist
26local vlist_code = nodecodes.vlist
27local glue_code = nodecodes.glue
28local penalty_code = nodecodes.penalty
29local glyph_code = nodecodes.glyph
30local par_code = nodecodes.par
31
32local indent_code = nodes.listcodes.indent
33
34local hmode_code = tex.modelevels.horizontal
35
36local nuts = nodes.nuts
37local tonut = nuts.tonut
38local tonode = nuts.tonode
39
40
41local getnext = nuts.getnext
42local getprev = nuts.getprev
43local getboth = nuts.getboth
44local getdisc = nuts.getdisc
45local getid = nuts.getid
46local getsubtype = nuts.getsubtype
47local getlist = nuts.getlist
48local getattribute = nuts.getattribute
49local getbox = nuts.getbox
50local getdirection = nuts.getdirection
51local getwidth = nuts.getwidth
52local getwhd = nuts.getwhd
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 flushnode = nuts.flushnode
71local flushlist = nuts.flushlist
72local copy_node = nuts.copy
73local copylist = nuts.copylist
74local find_tail = nuts.tail
75local getdimensions = nuts.dimensions
76local hpack = nuts.hpack
77local vpack = nuts.vpack
78local traverseid = nuts.traverseid
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 flushlist(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(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(copylist(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(copylist(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 flushnode(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 flushnode(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 flushlist = nuts.flushlist
487 local copylist = nuts.copylist
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 = copylist(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 flushlist(b)
547 end
548 local b = c[name]
549 if not b then
550
551 elseif copy then
552 b = copylist(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 flushlist(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 flushlist(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 flushlist(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 flushlist(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 traverseid(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 traverseid(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 flushlist(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
837implement {
838 name = "doifelseindented",
839 public = true,
840 protected = true,
841 actions = function()
842 local n = texgetnest()
843 local b = false
844 if n.mode == hmode_code then
845 n = tonut(n.head)
846 while n do
847 n = getnext(n)
848 if n then
849 local id = getid(n)
850 if id == hlist_code then
851 if getsubtype(n) == indent_code then
852 b = getwidth(n) > 0
853 break
854 end
855 elseif id ~= par_code then
856 break
857 end
858 end
859 end
860 end
861 commands.doifelse(b)
862 end,
863}
864
865implement {
866 name = "noflinesinbox",
867 public = true,
868 protected = false,
869 arguments = "integer",
870 actions = function(n)
871 local c = 0
872 local b = getbox(n)
873 if b then
874 b = getlist(b)
875 if b then
876 for n, id in traverse(b) do
877 if id == hlist_code or id == vlist_code then
878 c = c + 1
879 end
880 end
881 end
882 end
883 context(c)
884 end,
885}
886
887do
888
889 local takebox = tex.takebox
890
891 interfaces.implement {
892 name = "thebox",
893 public = true,
894 arguments = "integer",
895 actions = function(n)
896 context(takebox(n))
897 end
898 }
899
900end
901 |