1if not modules then modules = { } end modules ['typo-syn'] = {
2 version = 1.000,
3 optimize = true,
4 comment = "companion to typo-syn.mkxl",
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
13local nodes = nodes
14
15local tasks = nodes.tasks
16local enableaction = tasks.enableaction
17
18
19local nuts = nodes.nuts
20local tonut = nodes.tonut
21
22local getattr = nuts.getattr
23local getattrlist = nuts.getattrlist
24local getdepth = nuts.getdepth
25local getdirection = nuts.getdirection
26local getdisc = nuts.getdisc
27local getglue = nuts.getglue
28local getheight = nuts.getheight
29local getid = nuts.getid
30local getlist = nuts.getlist
31local getnext = nuts.getnext
32local getprev = nuts.getprev
33local getprop = nuts.getprop
34local getsubtype = nuts.getsubtype
35local gettotal = nuts.gettotal
36local getwhd = nuts.getwhd
37local getwidth = nuts.getwidth
38local setattrlist = nuts.setattrlist
39local setdepth = nuts.setdepth
40local setdisc = nuts.setdisc
41local setheight = nuts.setheight
42local setlink = nuts.setlink
43local setlist = nuts.setlist
44local setnext = nuts.setnext
45local setoffsets = nuts.setoffsets
46local setprev = nuts.setprev
47local setprop = nuts.setprop
48local setwidth = nuts.setwidth
49
50local hpack = nuts.hpack
51local rangedimensions = nuts.rangedimensions
52local insertbefore = nuts.insertbefore
53local insertafter = nuts.insertafter
54local removenode = nuts.remove
55local flushnode = nuts.flush
56
57local traverselist = nuts.traverselist
58
59local nodecodes = nodes.nodecodes
60
61local glyph_code <const> = nodecodes.glyph
62local rule_code <const> = nodecodes.rule
63local dir_code <const> = nodecodes.dir
64local disc_code <const> = nodecodes.disc
65local hlist_code <const> = nodecodes.hlist
66local vlist_code <const> = nodecodes.vlist
67local math_code <const> = nodecodes.math
68local glue_code <const> = nodecodes.glue
69local kern_code <const> = nodecodes.kern
70local penalty_code <const> = nodecodes.penalty
71
72local line_code <const> = nodes.listcodes.line
73local fontkern_code <const> = nodes.kerncodes.fontkern
74local parfillskip_code <const> = nodes.gluecodes.parfillrightskip
75local baselineskip_code <const> = nodes.gluecodes.baselineskip
76
77local new_direction = nuts.pool.direction
78
79local runningrule <const> = tex.magicconstants.runningrule
80
81
82
83local synchronize = typesetters.synchronize or { }
84typesetters.synchronize = synchronize or { }
85
86local registervalue = attributes.registervalue
87local getvalue = attributes.getvalue
88local hasvalues = attributes.hasvalues
89
90local a_synchronize <const> = attributes.private("synchronize")
91
92local trace = false
93
94local report = logs.reporter("parallel")
95
96local pushsavelevel = tex.pushsavelevel
97local popsavelevel = tex.popsavelevel
98local dontcomplain = tex.dontcomplain
99
100local index = 0
101local lastattr = nil
102local lastline = nil
103
104interfaces.implement {
105 name = "registersynchronize",
106 arguments = { "dimension", "dimension", "dimension", "box" },
107 actions = function(ht,dp,slack,box)
108 index = index + 1
109 box = tonut(box)
110 local t = {
111 index = index,
112 lineheight = ht,
113 linedepth = dp,
114 slack = slack,
115 height = getheight(height),
116 depth = getheight(depth),
117 box = box,
118 }
119 local v = registervalue(a_synchronize,t)
120 tex.setattribute(a_synchronize,v)
121 if index > 0 then
122 enableaction("vboxbuilders", "typesetters.synchronize.handler")
123 enableaction("mvlbuilders", "typesetters.synchronize.handler")
124 end
125 end,
126}
127
128
129
130local function hsplit(box,targetwidth,targetheight,targetdepth,mcriterium,pcriterium,upto)
131 local first = getlist(box)
132 local last = first
133 local current = first
134 local previous = current
135 local lastdisc = nil
136 local lastglyph = nil
137
138
139 local width = 0
140 local height = 0
141 local depth = 0
142 local dirstack = { }
143 local dirtop = 0
144 local minwidth = targetwidth
145 local maxwidth = targetwidth
146 local usedwidth = targetwidth
147 while true do
148 previous = current
149 local id = getid(current)
150 if id == glyph_code then
151 local wd, ht, dp = getwhd(current)
152
153 local newwidth = width + wd
154 if newwidth >= usedwidth then
155 if not lastdisc and lastglyph then
156 last = getprev(lastglyph)
157 end
158 break
159 else
160 width = newwidth
161 if ht > height then
162 height = ht
163 end
164 if dp > depth then
165 depth = dp
166 end
167 end
168 if not lastglyph then
169 lastglyph = current
170 end
171 elseif id == kern_code then
172 local wd = getwidth(current)
173 local newwidth = width + wd
174 if getsubtype(current) == fontkern_code then
175
176 width = newwidth
177 else
178 last = previous
179 if newwidth >= usedwidth then
180 break
181 else
182 width = newwidth
183 end
184 lastdisc = nil
185 lastglyph = nil
186 end
187 elseif id == disc_code then
188 local pre, post, replace = getdisc(current)
189 local wd = replace and rangedimensions(box,replace) or 0
190 local newwidth = width + wd
191 if newwidth >= usedwidth then
192 break
193 end
194 local wd = pre and rangedimensions(box,pre) or 0
195 local prewidth = width + wd
196 if prewidth >= usedwidth then
197 break
198 end
199 width = newwidth
200 lastdisc = current
201 else
202
203 if id == glue_code or id == math_code then
204
205 last = previous
206 local wd, more, less = getglue(current)
207 local newwidth = width + wd
208 if newwidth >= usedwidth then
209 break
210 else
211 width = newwidth
212
213
214
215 maxwidth = maxwidth + more
216 minwidth = minwidth + less
217
218 usedwidth = minwidth
219 end
220 elseif id == hlist_code or id == vlist_code then
221 last = previous
222 local wd, ht, dp = getwhd(current)
223 local newwidth = width + wd
224 if newwidth >= usedwidth then
225 break
226 else
227 width = newwidth
228 if ht > height then
229 height = ht
230 end
231 if dp > depth then
232 depth = dp
233 end
234 end
235 elseif id == rule_code then
236 last = previous
237 local wd, ht, dp = getwhd(current)
238 local newwidth = width + wd
239 if newwidth >= usedwidth then
240 break
241 else
242 width = newwidth
243 if ht ~= runningrule and ht > height then
244 height = ht
245 end
246 if dp ~= runningrule and dp > depth then
247 depth = dp
248 end
249 end
250 elseif id == dir_code then
251 local dir, cancel = getdirection(current)
252 if cancel then
253 if dirtop > 0 then
254 dirtop = dirtop - 1
255 end
256 else
257 dirtop = dirtop + 1
258 dirstack[dirtop] = dir
259 end
260 end
261 lastdisc = nil
262 lastglyph = nil
263 end
264 local next = getnext(current)
265 if next then
266 current = next
267 else
268 last = previous
269 break
270 end
271 end
272 local next
273 if lastdisc then
274 local pre, post, replace, pretail, posttail, replacetail = getdisc(lastdisc,true)
275 last = getprev(lastdisc)
276
277 next = getnext(lastdisc)
278 if next then
279 setprev(next)
280 end
281
282 setlink(last,pre)
283 last = pretail
284 if post then
285 setlink(posttail,next)
286 next = post
287 end
288 setdisc(lastdisc,nil,nil,replace)
289 flushnode(lastdisc)
290 else
291 next = getnext(last)
292 if next then
293 setprev(next)
294 end
295 setnext(last)
296 end
297 while last do
298 local id = getid(last)
299 if id == glue_code or id == penalty_code then
300
301
302
303
304
305
306
307
308
309
310
311 first, last = removenode(first,last,true)
312 else
313 break
314 end
315 end
316 if dirtop > 0 then
317 for i=dirtop,1,-1 do
318 local d = new_direction(dirstack[i],true)
319 first, last = insertafter(first,last,d)
320 end
321 for i=1,dirtop do
322 local d = new_direction(dirstack[i],false)
323 next = insertbefore(next,next,d)
324 end
325 end
326 if first then
327
328
329 local result
330 if upto then
331 result = hpack(first)
332 else
333 local badness, overshoot
334 result, badness, overshoot = hpack(first,targetwidth,"exactly")
335 local pdone = pcriterium and badness > pcriterium
336 local mdone = mcriterium and badness > mcriterium
337 if overshoot > 0 then
338 if pdone then
339 result = hpack(first)
340 end
341 elseif overshoot < 0 then
342 if mdone then
343 result = hpack(first)
344 end
345 else
346 if pdone or mdone then
347 result = hpack(first)
348 end
349 end
350 end
351
352 setattrlist(result,getattrlist(box))
353 setheight(result,targetheight or height)
354 setdepth(result,targetdepth or depth)
355 setlist(box,next)
356 setwidth(box,rangedimensions(box,next))
357 return result
358 end
359end
360
361do
362
363 local scanners = tokens.scanners
364 local scanword = scanners.word
365 local scaninteger = scanners.integer
366 local scandimension = scanners.dimension
367
368 local tonode = nuts.tonode
369 local getbox = nuts.getbox
370 local setbox = nuts.setbox
371
372 local direct_value <const> = tokens.values.direct
373 local none_value <const> = tokens.values.none
374
375 interfaces.implement {
376 name = "hsplit",
377 protected = true,
378 public = true,
379 usage = "value",
380 actions = function(what)
381 local n = scaninteger()
382 local w = 0
383 local h = false
384 local d = false
385 local pcriterium = false
386 local mcriterium = false
387 local upto = false
388 while true do
389 local key = scanword()
390 if key == "to" then
391 upto = false
392 w = scandimension()
393 elseif key == "upto" then
394 upto = true
395 w = scandimension()
396 elseif key == "width" then
397 w = scandimension()
398 elseif key == "height" then
399 h = scandimension()
400 elseif key == "depth" then
401 d = scandimension()
402 elseif key == "criterium" then
403 pcriterium = scaninteger()
404 mcriterium = scaninteger()
405 elseif key == "shrinkcriterium" then
406 mcriterium = scaninteger()
407 elseif key == "stretchcriterium" then
408 pcriterium = scaninteger()
409 else
410 break
411 end
412 end
413 pushsavelevel()
414 dontcomplain()
415 local r = hsplit(getbox(n),w,h,d,mcriterium,pcriterium,upto)
416 popsavelevel()
417 if r then
418 if what == "value" then
419 return direct_value, r
420 else
421 context(tonode(r))
422 end
423 else
424 setbox(n)
425 if what == "value" then
426 return none_value, nil
427 end
428 end
429 end,
430 }
431
432end
433
434local function getproperties(parent)
435 local props = getprop(parent,"parallel")
436 if not props then
437 local w, h, d = getwhd(parent)
438 props = {
439 width = w,
440 height = h,
441 depth = d,
442 }
443 setprop(parent,"parallel",props)
444 end
445 return props
446end
447
448local function setproperties(parent,data,result,level,ctotal)
449 local props = getproperties(parent)
450 local depth = props.depth
451 local height = props.height
452 local delta = data.linedepth - depth
453 if delta > 0 then
454 depth = data.linedepth
455 setdepth(parent,depth)
456 props.depth = depth
457 local n = getnext(parent)
458 if n and getid(n) == glue_code and getsubtype(n) == baselineskip_code then
459 setwidth(n,getwidth(n) - delta)
460 end
461 end
462
463
464
465
466
467 local offset = level * ctotal
468 if props.depth + offset > depth then
469 setdepth(parent,props.depth+offset)
470 end
471 setoffsets(result,0,-offset)
472 setwidth(result,0)
473end
474
475local function flush(head,first,last,a,parent,nesting)
476 if first and nesting == 0 then
477 local data = getvalue(a_synchronize,a)
478 local upto = getnext(last)
479 if upto and getid(upto) == penalty_code then
480 upto = getnext(upto)
481 end
482 if upto and getid(upto) == glue_code and getsubtype(upto) == parfillskip_code then
483 upto = getnext(upto)
484 end
485 local props = getproperties(parent)
486 local width = rangedimensions(parent,first,upto)
487 if width > props.width then
488 width = props.width
489 end
490 local content = data.box
491 local index = data.index
492 if not content then
493 if trace then
494 report("index %i, verdict %a",index,"done")
495 end
496 else
497 local result = nil
498 local cwidth = getwidth(content)
499 local ctotal = gettotal(content)
500 if cwidth <= width then
501 if trace then
502 report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"fit")
503 end
504 result = content
505 data.box = nil
506 elseif cwidth > width then
507 if trace then
508 report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"overflow")
509 end
510 result = hsplit(content,width-(data.slack or 0),nil,nil,200)
511 lastattr = a
512 lastline = parent
513 else
514 report("index %i, verdict %a",index,"weird")
515 end
516 if result then
517 setproperties(parent,data,result,1,ctotal)
518 head = insertbefore(head,first,result)
519 end
520 end
521 end
522 return head
523end
524
525local function lastflush(lastline,lastattr)
526 local data = getvalue(a_synchronize,lastattr)
527 if not data then
528 return
529 end
530 local content = data.box
531 if not content or getwidth(content) == 0 then
532 return
533 end
534 local head = getlist(lastline)
535 if not head then
536 return
537 end
538 local first = head
539 local last = nil
540 local props = getproperties(lastline)
541 local width = props.width
542 local height = props.height
543 local depth = props.depth
544 local level = 1
545 if depth < data.linedepth then
546 depth = data.linedepth
547 setdepth(lastline,depth)
548 end
549 if height < data.lineheight then
550 height = data.lineheight
551 setheight(lastline,height)
552 end
553 while true do
554 local content = data.box
555 local index = data.index
556 if content then
557 local result = nil
558 local total = 0
559 local cwidth = getwidth(content)
560 local ctotal = gettotal(content)
561 if cwidth <= width then
562 if trace then
563 report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"fit")
564 end
565 result = content
566 data.box = nil
567 elseif cwidth > width then
568 if trace then
569 report("index %i, available %p, content %p, verdict %a",index,width,cwidth,"overflow")
570 end
571 result = hsplit(content,width-(data.slack or 0),nil,nil,200)
572 else
573 report("index %i, verdict %a",index,"weird")
574 end
575 if result then
576 level = level + 1
577 setproperties(lastline,data,result,level,ctotal)
578 head = insertbefore(head,first,result)
579 setlist(lastline,head)
580 else
581 break
582 end
583 else
584 break
585 end
586 end
587end
588
589local processranges = nuts.processranges
590
591function synchronize.handler(head,where)
592 if where == "hmodepar" and hasvalues(a_synchronize) then
593 lastattr = nil
594 lastline = nil
595 for n, id, subtype in traverselist(head) do
596 if subtype == line_code then
597 lastattr = nil
598 local list = getlist(n)
599 local head = processranges(a_synchronize,flush,list,n)
600 if head ~= list then
601 setlist(n,head)
602 end
603 end
604 end
605 if lastattr and lastline then
606 lastflush(lastline,lastattr)
607 end
608 end
609 return head
610end
611
612
613
614local settings_to_array = utilities.parsers.settings_to_array
615local get_buffer_content = buffers.getcontent
616local splitlines = string.splitlines
617
618interfaces.implement {
619 name = "synchronizesteps",
620 arguments = { {
621 { "list" },
622 { "split" },
623 { "buffer" },
624 { "text" },
625 } },
626 actions = function(t)
627 local split = t.split
628 local list = t.list
629 local buffer = t.buffer
630 local text = t.text
631 local data = false
632 if buffer and buffer ~= "" then
633 data = settings_to_array(buffer)
634 if #data == 2 then
635 for i=1,#data do
636 data[i] = splitlines(get_buffer_content(data[i]) or "")
637 end
638 else
639 return
640 end
641 elseif text and text ~= "" then
642 data = settings_to_array(text)
643 if #data == 2 then
644 for i=1,#data do
645 data[i] = settings_to_array(data[i])
646 end
647 else
648 return
649 end
650 else
651 return
652 end
653 if list and list ~= "" then
654 list = settings_to_array(list)
655 else
656 list = { }
657 end
658 local done = data[1]
659 local dtwo = data[2]
660 if #done == #dtwo then
661 local lone = list[1] or ""
662 local ltwo = list[2] or ""
663 for i=1,#done do
664 context.dosplitsynchronize(lone,ltwo,done[i],dtwo[i])
665 end
666 else
667 context.type("[different sizes in synchronize]")
668 end
669 end,
670}
671 |