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