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