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