1if not modules then modules = { } end modules ['node-rul'] = {
2 version = 1.001,
3 optimize = true,
4 comment = "companion to node-rul.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
10
11
12
13
14
15
16
17
18
19
20
21local tonumber = tonumber
22
23local context = context
24local attributes = attributes
25local nodes = nodes
26local properties = nodes.properties.data
27
28local enableaction = nodes.tasks.enableaction
29
30local nuts = nodes.nuts
31local tonode = nuts.tonode
32local tonut = nuts.tonut
33
34local setnext = nuts.setnext
35local setprev = nuts.setprev
36local setlink = nuts.setlink
37local getnext = nuts.getnext
38local getprev = nuts.getprev
39local getid = nuts.getid
40local getdirection = nuts.getdirection
41local getattr = nuts.getattr
42local setattr = nuts.setattr
43local getfont = nuts.getfont
44local getsubtype = nuts.getsubtype
45local getlist = nuts.getlist
46local setwhd = nuts.setwhd
47local setattrlist = nuts.setattrlist
48local setshift = nuts.setshift
49local getwidth = nuts.getwidth
50local setwidth = nuts.setwidth
51local setoffsets = nuts.setoffsets
52local setfield = nuts.setfield
53
54
55
56local getruledata = nuts.getruledata
57
58local isglyph = nuts.isglyph
59
60local flushlist = nuts.flushlist
61local effectiveglue = nuts.effectiveglue
62local insertnodeafter = nuts.insertafter
63local insertnodebefore = nuts.insertbefore
64local find_tail = nuts.tail
65local setglue = nuts.setglue
66local getrangedimensions = nuts.rangedimensions
67local hpack_nodes = nuts.hpack
68local copylist = nuts.copylist
69
70local nexthlist = nuts.traversers.hlist
71
72local nodecodes = nodes.nodecodes
73local rulecodes = nodes.rulecodes
74local gluecodes = nodes.gluecodes
75local listcodes = nodes.listcodes
76
77local glyph_code = nodecodes.glyph
78local par_code = nodecodes.par
79local dir_code = nodecodes.dir
80local glue_code = nodecodes.glue
81local hlist_code = nodecodes.hlist
82
83local indentlist_code = listcodes.indent
84local linelist_code = listcodes.line
85
86local leftskip_code = gluecodes.leftskip
87local rightskip_code = gluecodes.rightskip
88local parfillskip_code = gluecodes.parfillskip
89
90local nodepool = nuts.pool
91
92local new_rule = nodepool.rule
93local new_userrule = nodepool.userrule
94local new_kern = nodepool.kern
95local new_leader = nodepool.leader
96
97local n_tostring = nodes.idstostring
98local n_tosequence = nodes.tosequence
99
100local variables = interfaces.variables
101local implement = interfaces.implement
102
103local privateattributes = attributes.private
104
105local a_ruled = privateattributes('ruled')
106local a_runningtext = privateattributes('runningtext')
107local a_color = privateattributes('color')
108local a_transparency = privateattributes('transparency')
109local a_colormodel = privateattributes('colormodel')
110local a_linefiller = privateattributes("linefiller")
111local a_viewerlayer = privateattributes("viewerlayer")
112
113local v_both = variables.both
114local v_left = variables.left
115local v_right = variables.right
116local v_local = variables["local"]
117local v_yes = variables.yes
118local v_foreground = variables.foreground
119
120local fonthashes = fonts.hashes
121local fontdata = fonthashes.identifiers
122local fontresources = fonthashes.resources
123
124local dimenfactor = fonts.helpers.dimenfactor
125local splitdimen = number.splitdimen
126local setmetatableindex = table.setmetatableindex
127
128local magicconstants = tex.magicconstants
129local running = magicconstants.running
130
131
132
133local striprange = nuts.striprange
134local processwords = nuts.processwords
135
136
137
138local rules = nodes.rules or { }
139nodes.rules = rules
140rules.data = rules.data or { }
141
142local nutrules = nuts.rules or { }
143nuts.rules = nutrules
144
145storage.register("nodes/rules/data", rules.data, "nodes.rules.data")
146
147local data = rules.data
148
149
150
151local function usernutrule(t,noattributes)
152 local r = new_userrule(t.width or 0,t.height or 0,t.depth or 0)
153 if noattributes == false or noattributes == nil then
154
155 else
156 setattrlist(r,true)
157 end
158 properties[r] = t
159 return r
160end
161
162nutrules.userrule = usernutrule
163
164local function userrule(t,noattributes)
165 return tonode(usernutrule(t,noattributes))
166end
167
168rules.userrule = userrule
169local ruleactions = { }
170
171rules .ruleactions = ruleactions
172nutrules.ruleactions = ruleactions
173
174local function mathaction(n,h,v,what)
175 local font = getruledata(n)
176 local actions = fontresources[font].mathruleactions
177 if actions then
178 local action = actions[what]
179 if action then
180 action(n,h,v,font)
181 end
182 end
183end
184
185local function mathradical(n,h,v)
186 mathaction(n,h,v,"radicalaction")
187end
188
189local function mathrule(n,h,v)
190 mathaction(n,h,v,"hruleaction")
191end
192
193local x
194
195local function useraction(n,h,v)
196 local p = properties[n]
197 if p then
198 local i = p.type or "draw"
199 local a = ruleactions[i]
200 if a then
201 a(p,h,v,i,n)
202 end
203 end
204end
205
206local subtypeactions = {
207 [rulecodes.user] = useraction,
208 [rulecodes.over] = mathrule,
209 [rulecodes.under] = mathrule,
210 [rulecodes.fraction] = mathrule,
211 [rulecodes.radical] = mathradical,
212}
213
214local function process_rule(n,h,v)
215 local n = tonut(n)
216 local s = getsubtype(n)
217 local a = subtypeactions[s]
218 if a then
219 a(n,h,v)
220 end
221end
222
223callbacks.register("process_rule",process_rule,"handle additional user rule features")
224
225callbacks.functions.process_rule = process_rule
226
227
228
229local trace_ruled = false trackers.register("nodes.rules", function(v) trace_ruled = v end)
230local report_ruled = logs.reporter("nodes","rules")
231
232function rules.define(settings)
233 local nofdata = #data + 1
234 data[nofdata] = settings
235 local text = settings.text
236 if text then
237 local b = nuts.takebox(text)
238 if b then
239 nodepool.register(b)
240 settings.text = getlist(b)
241 else
242 settings.text = nil
243 end
244 end
245 return nofdata
246end
247
248local function flush_ruled(head,f,l,d,level,parent,strip)
249 local font = nil
250 local char, id = isglyph(f)
251 if char then
252 font = id
253 elseif id == hlist_code then
254 font = getattr(f,a_runningtext)
255 end
256 if not font then
257
258 return head
259 end
260 local r, m
261 if strip then
262 if trace_ruled then
263 local before = n_tosequence(f,l,true)
264 f, l = striprange(f,l)
265 local after = n_tosequence(f,l,true)
266 report_ruled("range stripper, before %a, after %a",before,after)
267 else
268 f, l = striprange(f,l)
269 end
270 end
271 if not f then
272 return head
273 end
274 local wd, ht, dp = getrangedimensions(parent,f,getnext(l))
275 local method = d.method
276 local empty = d.empty == v_yes
277 local offset = d.offset
278 local dy = d.dy
279 local order = d.order
280 local max = d.max
281 local mp = d.mp
282 local rulethickness = d.rulethickness
283 local unit = d.unit
284 local ma = d.ma
285 local ca = d.ca
286 local ta = d.ta
287 local colorspace = ma > 0 and ma or getattr(f,a_colormodel) or 1
288 local color = ca > 0 and ca or getattr(f,a_color)
289 local transparency = ta > 0 and ta or getattr(f,a_transparency)
290 local foreground = order == v_foreground
291 local layer = getattr(f,a_viewerlayer)
292 local e = dimenfactor(unit,font)
293 local rt = tonumber(rulethickness)
294 if rt then
295 rulethickness = e * rulethickness / 2
296 else
297 local n, u = splitdimen(rulethickness)
298 if n and u then
299 rulethickness = n * dimenfactor(u,fontdata[font]) / 2
300 else
301 rulethickness = 1/5
302 end
303 end
304
305 if level > max then
306 level = max
307 end
308 if method == 0 then
309 offset = 2*offset
310 m = (offset+(level-1)*dy)*e/2 + rulethickness/2
311 else
312 m = 0
313 end
314
315 local function inject(r,wd,ht,dp)
316 if layer then
317 setattr(r,a_viewerlayer,layer)
318 end
319 if empty then
320 head = insertnodebefore(head,f,r)
321 setlink(r,getnext(l))
322 setprev(f)
323 setnext(l)
324 flushlist(f)
325 else
326 local k = new_kern(-wd)
327 if foreground then
328 insertnodeafter(head,l,k)
329 insertnodeafter(head,k,r)
330 l = r
331 else
332 head = insertnodebefore(head,f,r)
333 insertnodeafter(head,r,k)
334 end
335 end
336 if trace_ruled then
337 report_ruled("level %a, width %p, height %p, depth %p, nodes %a, text %a",
338 level,wd,ht,dp,n_tostring(f,l),n_tosequence(f,l,true))
339 end
340 end
341 if mp and mp ~= "" then
342 local r = usernutrule {
343 width = wd,
344 height = ht,
345 depth = dp,
346 type = "mp",
347 factor = e,
348 offset = offset,
349 line = rulethickness,
350 data = mp,
351 ma = colorspace,
352 ca = color,
353 ta = transparency,
354 }
355 inject(r,wd,ht,dp)
356 else
357 local tx = d.text
358 if tx then
359 local l = copylist(tx)
360 if d["repeat"] == v_yes then
361 l = new_leader(wd,l)
362 setattrlist(l,tx)
363 end
364 l = hpack_nodes(l,wd,"exactly")
365 inject(l,wd,ht,dp)
366 else
367 for i=1,level do
368 local hd = (offset+(i-1)*dy)*e - m
369
370
371 local ht = hd + rulethickness
372 local dp = -hd + rulethickness
373 local r = new_rule(wd,ht,dp)
374
375 if color then
376 setattr(r,a_colormodel,colorspace)
377 setattr(r,a_color,color)
378 end
379 if transparency then
380 setattr(r,a_transparency,transparency)
381 end
382 inject(r,wd,ht,dp)
383 end
384 end
385 end
386 return head
387end
388
389rules.handler = function(head)
390 return processwords(a_ruled,data,flush_ruled,head)
391end
392
393function rules.enable()
394 enableaction("shipouts","nodes.rules.handler")
395end
396
397local trace_shifted = false trackers.register("nodes.shifting", function(v) trace_shifted = v end)
398
399local report_shifted = logs.reporter("nodes","shifting")
400
401local a_shifted = attributes.private('shifted')
402
403local shifts = nodes.shifts or { }
404nodes.shifts = shifts
405shifts.data = shifts.data or { }
406
407storage.register("nodes/shifts/data", shifts.data, "nodes.shifts.data")
408
409local data = shifts.data
410
411function shifts.define(settings)
412 local nofdata = #data + 1
413 data[nofdata] = settings
414 return nofdata
415end
416
417local function flush_shifted(head,first,last,data,level,parent,strip)
418 if true then
419 first, last = striprange(first,last)
420 end
421 local prev = getprev(first)
422 local next = getnext(last)
423 setprev(first)
424 setnext(last)
425 local width, height, depth = getrangedimensions(parent,first,next)
426 local list = hpack_nodes(first,width,"exactly")
427 if first == head then
428 head = list
429 end
430 if prev then
431 setlink(prev,list)
432 end
433 if next then
434 setlink(list,next)
435 end
436 local raise = data.dy * dimenfactor(data.unit,fontdata[getfont(first)])
437 setshift(list,raise)
438 setwhd(list,width,height,depth)
439 if trace_shifted then
440 report_shifted("width %p, nodes %a, text %a",width,n_tostring(first,last),n_tosequence(first,last,true))
441 end
442 return head
443end
444
445shifts.handler = function(head)
446 return processwords(a_shifted,data,flush_shifted,head)
447end
448
449function shifts.enable()
450 enableaction("shipouts","nodes.shifts.handler")
451end
452
453
454
455local linefillers = nodes.linefillers or { }
456nodes.linefillers = linefillers
457linefillers.data = linefillers.data or { }
458
459storage.register("nodes/linefillers/data", linefillers.data, "nodes.linefillers.data")
460
461local data = linefillers.data
462
463function linefillers.define(settings)
464 local nofdata = #data + 1
465 data[nofdata] = settings
466 return nofdata
467end
468
469local function linefiller(current,data,width,location)
470 local height = data.height
471 local depth = data.depth
472 local mp = data.mp
473 local ma = data.ma
474 local ca = data.ca
475 local ta = data.ta
476 if mp and mp ~= "" then
477 return usernutrule {
478 width = width,
479 height = height,
480 depth = depth,
481 type = "mp",
482 line = data.rulethickness,
483 data = mp,
484 ma = ma,
485 ca = ca,
486 ta = ta,
487 option = location,
488 direction = getdirection(current),
489 }
490 else
491 local rule = new_rule(width,height,depth)
492 if ca then
493 setattr(rule,a_colorspace,ma)
494 setattr(rule,a_color,ca)
495 end
496 if ta then
497 setattr(rule,a_transparency,ta)
498 end
499 return rule
500 end
501end
502
503function linefillers.filler(current,data,width,height,depth)
504 if width and width > 0 then
505 local height = height or data.height or 0
506 local depth = depth or data.depth or 0
507 if (height + depth) ~= 0 then
508 local mp = data.mp
509 local ma = data.ma
510 local ca = data.ca
511 local ta = data.ta
512 if mp and mp ~= "" then
513 return usernutrule {
514 width = width,
515 height = height,
516 depth = depth,
517 type = "mp",
518 line = data.rulethickness,
519 data = mp,
520 ma = ma,
521 ca = ca,
522 ta = ta,
523 option = location,
524 direction = getdirection(current),
525 }
526 else
527 local rule = new_rule(width,height,depth)
528 if ca then
529 setattr(rule,a_colorspace,ma)
530 setattr(rule,a_color,ca)
531 end
532 if ta then
533 setattr(rule,a_transparency,ta)
534 end
535 return rule
536 end
537 end
538 end
539end
540
541local function find_attr(head,attr)
542 while head do
543 local a = head[attr]
544 if a then
545 return a, head
546 end
547 head = getnext(head)
548 end
549end
550
551function linefillers.handler(head)
552 for current, subtype in nexthlist, head do
553 if current and subtype == linelist_code then
554
555
556 local a = getattr(current,a_linefiller)
557 if a then
558 local class = a % 1000
559 local data = data[class]
560 if data then
561 local location = data.location
562 local scope = data.scope
563 local distance = data.distance
564 local threshold = data.threshold
565 local leftlocal = false
566 local rightlocal = false
567
568 if scope == v_right then
569 leftlocal = true
570 elseif scope == v_left then
571 rightlocal = true
572 elseif scope == v_local then
573 leftlocal = true
574 rightlocal = true
575 end
576
577 local list = getlist(current)
578
579 if location == v_left or location == v_both then
580 local lskip = nil
581 local iskip = nil
582 local head = list
583 while head do
584 local id = getid(head)
585 if id == glue_code then
586 if getsubtype(head) == leftskip_code then
587 lskip = head
588 else
589 break
590 end
591 elseif id == par_code or id == dir_code then
592
593 elseif id == hlist_code then
594 if getsubtype(head) == indentlist_code then
595 iskip = head
596 end
597 break
598 else
599 break
600 end
601 head = getnext(head)
602 end
603 if head then
604 local indentation = iskip and getwidth(iskip) or 0
605 local leftfixed = lskip and getwidth(lskip) or 0
606 local lefttotal = lskip and effectiveglue(lskip,current) or 0
607 local width = lefttotal - (leftlocal and leftfixed or 0) + indentation - distance
608 if width > threshold then
609 if iskip then
610 setwidth(iskip,0)
611 end
612 if lskip then
613 setglue(lskip,leftlocal and getwidth(lskip) or nil)
614 if distance > 0 then
615 insertnodeafter(list,lskip,new_kern(distance))
616 end
617 insertnodeafter(list,lskip,linefiller(current,data,width,"left"))
618 else
619 insertnodebefore(list,head,linefiller(current,data,width,"left"))
620 if distance > 0 then
621 insertnodebefore(list,head,new_kern(distance))
622 end
623 end
624 end
625 end
626 end
627
628 if location == v_right or location == v_both then
629 local pskip = nil
630 local rskip = nil
631 local tail = find_tail(list)
632 while tail and getid(tail) == glue_code do
633 local subtype = getsubtype(tail)
634 if subtype == rightskip_code then
635 rskip = tail
636 elseif subtype == parfillskip_code then
637 pskip = tail
638 else
639 break
640 end
641 tail = getprev(tail)
642 end
643 if tail then
644 local rightfixed = rskip and getwidth(rskip) or 0
645 local righttotal = rskip and effectiveglue(rskip,current) or 0
646 local parfixed = pskip and getwidth(pskip) or 0
647 local partotal = pskip and effectiveglue(pskip,current) or 0
648 local width = righttotal - (rightlocal and rightfixed or 0) + partotal - distance
649 if width > threshold then
650 if pskip then
651 setglue(pskip)
652 end
653 if rskip then
654 setglue(rskip,rightlocal and getwidth(rskip) or nil)
655 if distance > 0 then
656 insertnodebefore(list,rskip,new_kern(distance))
657 end
658 insertnodebefore(list,rskip,linefiller(current,data,width,"right"))
659 else
660 insertnodeafter(list,tail,linefiller(current,data,width,"right"))
661 if distance > 0 then
662 insertnodeafter(list,tail,new_kern(distance))
663 end
664 end
665 end
666 end
667 end
668 end
669 end
670 end
671 end
672 return head
673end
674
675local enable = false
676
677function linefillers.enable()
678 if not enable then
679
680 enableaction("finalizers","nodes.linefillers.handler")
681 enable = true
682 end
683end
684
685
686
687implement {
688 name = "definerule",
689 actions = { rules.define, context },
690 arguments = {
691 {
692 { "continue" },
693 { "unit" },
694 { "order" },
695 { "method", "integer" },
696 { "offset", "number" },
697 { "rulethickness" },
698 { "dy", "number" },
699 { "max", "number" },
700 { "ma", "integer" },
701 { "ca", "integer" },
702 { "ta", "integer" },
703 { "mp" },
704 { "empty" },
705 { "text", "integer" },
706 { "repeat" },
707 }
708 }
709}
710
711implement {
712 name = "enablerules",
713 onlyonce = true,
714 actions = rules.enable
715}
716
717implement {
718 name = "defineshift",
719 actions = { shifts.define, context },
720 arguments = {
721 {
722 { "continue" },
723 { "unit" },
724 { "method", "integer" },
725 { "dy", "number" },
726 }
727 }
728}
729
730implement {
731 name = "enableshifts",
732 onlyonce = true,
733 actions = shifts.enable
734}
735
736implement {
737 name = "definelinefiller",
738 actions = { linefillers.define, context },
739 arguments = {
740 {
741 { "method", "integer" },
742 { "location", "string" },
743 { "scope", "string" },
744 { "mp", "string" },
745 { "ma", "integer" },
746 { "ca", "integer" },
747 { "ta", "integer" },
748 { "depth", "dimension" },
749 { "height", "dimension" },
750 { "distance", "dimension" },
751 { "threshold", "dimension" },
752 { "rulethickness", "dimension" },
753 }
754 }
755}
756
757implement {
758 name = "enablelinefillers",
759 onlyonce = true,
760 actions = linefillers.enable
761}
762
763
764
765interfaces.implement {
766 name = "autorule",
767 arguments = {
768 {
769 { "width", "dimension" },
770 { "height", "dimension" },
771 { "depth", "dimension" },
772 { "xoffset", "dimension" },
773 { "yoffset", "dimension" },
774 { "left", "dimension" },
775 { "right", "dimension" },
776 },
777 },
778 actions = function(t)
779 local n = new_rule(
780 t.width or running,
781 t.height or running,
782 t.depth or running
783 )
784 setattrlist(n,true)
785 setoffsets(n,t.xoffset,t.yoffset)
786 local l = t.left
787 local r = t.right
788 if l then
789 setfield(n,"left",l)
790 end
791 if r then
792 setfield(n,"right",r)
793 end
794 context(tonode(n))
795 end
796}
797 |