1if not modules then modules = { } end modules ['font-sol'] = {
2 version = 1.001,
3 comment = "companion to font-sol.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23local gmatch, concat, format, remove = string.gmatch, table.concat, string.format, table.remove
24local next, tostring, tonumber = next, tostring, tonumber
25local insert, remove = table.insert, table.remove
26local getrandom = utilities.randomizer.get
27
28local utilities, logs, statistics, fonts, trackers = utilities, logs, statistics, fonts, trackers
29local interfaces, commands, attributes = interfaces, commands, attributes
30local nodes, node, tex = nodes, node, tex
31
32local trace_split = false trackers.register("builders.paragraphs.solutions.splitters.splitter", function(v) trace_split = v end)
33local trace_optimize = false trackers.register("builders.paragraphs.solutions.splitters.optimizer", function(v) trace_optimize = v end)
34local trace_colors = false trackers.register("builders.paragraphs.solutions.splitters.colors", function(v) trace_colors = v end)
35local trace_goodies = false trackers.register("fonts.goodies", function(v) trace_goodies = v end)
36
37local report_solutions = logs.reporter("fonts","solutions")
38local report_splitters = logs.reporter("fonts","splitters")
39local report_optimizers = logs.reporter("fonts","optimizers")
40
41local variables = interfaces.variables
42
43local v_normal = variables.normal
44local v_reverse = variables.reverse
45local v_preroll = variables.preroll
46local v_random = variables.random
47local v_split = variables.split
48
49local implement = interfaces.implement
50
51local settings_to_array = utilities.parsers.settings_to_array
52local settings_to_hash = utilities.parsers.settings_to_hash
53
54local tasks = nodes.tasks
55
56local nuts = nodes.nuts
57
58local getfield = nuts.getfield
59local getnext = nuts.getnext
60local getprev = nuts.getprev
61local getid = nuts.getid
62local getattr = nuts.getattr
63local getfont = nuts.getfont
64local getsubtype = nuts.getsubtype
65local getlist = nuts.getlist
66local getdirection = nuts.getdirection
67local getwidth = nuts.getwidth
68local getdata = nuts.getdata
69
70local getboxglue = nuts.getboxglue
71
72local setattr = nuts.setattr
73local setlink = nuts.setlink
74local setnext = nuts.setnext
75local setlist = nuts.setlist
76
77local find_node_tail = nuts.tail
78local flushnode = nuts.flushnode
79local flushnodelist = nuts.flushlist
80local copy_node_list = nuts.copylist
81local hpack_nodes = nuts.hpack
82local insertnodebefore = nuts.insertbefore
83local insertnodeafter = nuts.insertafter
84local protectglyphs = nuts.protectglyphs
85local startofpar = nuts.startofpar
86
87local nextnode = nuts.traversers.next
88local nexthlist = nuts.traversers.hlist
89local nextwhatsit = nuts.traversers.whatsit
90
91local repack_hlist = nuts.repackhlist
92
93local nodes_to_utf = nodes.listtoutf
94
95
96
97local setnodecolor = nodes.tracers.colors.set
98
99local nodecodes = nodes.nodecodes
100local whatsitcodes = nodes.whatsitcodes
101local kerncodes = nodes.kerncodes
102
103local glyph_code = nodecodes.glyph
104local disc_code = nodecodes.disc
105local kern_code = nodecodes.kern
106local hlist_code = nodecodes.hlist
107local dir_code = nodecodes.dir
108local par_code = nodecodes.par
109
110local whatsit_code = nodecodes.whatsit
111
112local fontkern_code = kerncodes.fontkern
113
114local righttoleft_code = (tex.directioncodes and tex.directioncodes.righttoleft) or nodes.dirvalues.righttoleft
115
116local userdefinedwhatsit_code = whatsitcodes.userdefined
117
118local nodeproperties = nodes.properties.data
119
120local nodepool = nuts.pool
121local usernodeids = nodepool.userids
122
123local new_direction = nodepool.direction
124local new_usernode = nodepool.usernode
125local new_glue = nodepool.glue
126local new_leftskip = nodepool.leftskip
127
128local starttiming = statistics.starttiming
129local stoptiming = statistics.stoptiming
130
131local inject_kerns = nodes.injections.handler
132
133local fonthashes = fonts.hashes
134local setfontdynamics = fonthashes.setdynamics
135
136local texsetattribute = tex.setattribute
137local unsetvalue = attributes.unsetvalue
138
139local parbuilders = builders.paragraphs
140parbuilders.solutions = parbuilders.solutions or { }
141local parsolutions = parbuilders.solutions
142parsolutions.splitters = parsolutions.splitters or { }
143local splitters = parsolutions.splitters
144
145local solutions = { }
146local registered = { }
147splitters.registered = registered
148
149local a_split = attributes.private('splitter')
150
151local preroll = true
152local criterium = 0
153local randomseed = nil
154local optimize = nil
155local variant = v_normal
156local splitwords = true
157
158local cache = { }
159local variants = { }
160local max_less = 0
161local max_more = 0
162
163local stack = { }
164
165local dummy = {
166 attribute = unsetvalue,
167 randomseed = 0,
168 criterium = 0,
169 preroll = false,
170 optimize = nil,
171 splitwords = false,
172 variant = v_normal,
173}
174
175local function checksettings(r,settings)
176 local s = r.settings
177 local method = settings_to_array(settings.method or "")
178 local optimize, preroll, splitwords
179 for i=1,#method do
180 local k = method[i]
181 if k == v_preroll then
182 preroll = true
183 elseif k == v_split then
184 splitwords = true
185 elseif variants[k] then
186 variant = k
187 optimize = variants[k]
188 end
189 end
190 r.randomseed = tonumber(settings.randomseed) or s.randomseed or r.randomseed or 0
191 r.criterium = tonumber(settings.criterium ) or s.criterium or r.criterium or 0
192 r.preroll = preroll or false
193 r.splitwords = splitwords or false
194 r.optimize = optimize or s.optimize or r.optimize or variants[v_normal]
195end
196
197local function pushsplitter(name,settings)
198 local r = name and registered[name]
199 if r then
200 if settings then
201 checksettings(r,settings)
202 end
203 else
204 r = dummy
205 end
206 insert(stack,r)
207
208 randomseed = r.randomseed or 0
209 criterium = r.criterium or 0
210 preroll = r.preroll or false
211 optimize = r.optimize or nil
212 splitwords = r.splitwords or nil
213
214 texsetattribute(a_split,r.attribute)
215 return #stack
216end
217
218local function popsplitter()
219 remove(stack)
220 local n = #stack
221 local r = stack[n] or dummy
222
223 randomseed = r.randomseed or 0
224 criterium = r.criterium or 0
225 preroll = r.preroll or false
226 optimize = r.optimize or nil
227
228 texsetattribute(a_split,r.attribute)
229 return n
230end
231
232local contextsetups = fonts.specifiers.contextsetups
233
234local function convert(featuresets,name,list)
235 if list then
236 local numbers = { }
237 local nofnumbers = 0
238 for i=1,#list do
239 local feature = list[i]
240 local fs = featuresets[feature]
241 local fn = fs and fs.number
242 if not fn then
243
244 fs = contextsetups[feature]
245 fn = fs and fs.number
246 end
247 if fn then
248 nofnumbers = nofnumbers + 1
249 numbers[nofnumbers] = fn
250 if trace_goodies or trace_optimize then
251 report_solutions("solution %a of %a uses feature %a with number %s",i,name,feature,fn)
252 end
253 else
254 report_solutions("solution %a of %a has an invalid feature reference %a",i,name,feature)
255 end
256 end
257 return nofnumbers > 0 and numbers
258 end
259end
260
261local function initialize(goodies)
262 local solutions = goodies.solutions
263 if solutions then
264 local featuresets = goodies.featuresets
265 local goodiesname = goodies.name
266 if trace_goodies or trace_optimize then
267 report_solutions("checking solutions in %a",goodiesname)
268 end
269 for name, set in next, solutions do
270 set.less = convert(featuresets,name,set.less)
271 set.more = convert(featuresets,name,set.more)
272 end
273 end
274end
275
276fonts.goodies.register("solutions",initialize)
277
278function splitters.define(name,settings)
279 local goodies = settings.goodies
280 local solution = settings.solution
281 local less = settings.less
282 local more = settings.more
283 local less_set, more_set
284 local l = less and settings_to_array(less)
285 local m = more and settings_to_array(more)
286 if goodies then
287 goodies = fonts.goodies.load(goodies)
288 if goodies then
289 local featuresets = goodies.featuresets
290 local solution = solution and goodies.solutions[solution]
291 if l and #l > 0 then
292 less_set = convert(featuresets,name,less)
293 else
294 less_set = solution and solution.less
295 end
296 if m and #m > 0 then
297 more_set = convert(featuresets,name,more)
298 else
299 more_set = solution and solution.more
300 end
301 end
302 else
303 if l then
304 local n = #less_set
305 for i=1,#l do
306 local ss = contextsetups[l[i]]
307 if ss then
308 n = n + 1
309 less_set[n] = ss.number
310 end
311 end
312 end
313 if m then
314 local n = #more_set
315 for i=1,#m do
316 local ss = contextsetups[m[i]]
317 if ss then
318 n = n + 1
319 more_set[n] = ss.number
320 end
321 end
322 end
323 end
324 if trace_optimize then
325 report_solutions("defining solutions %a, less %a, more %a",name,concat(less_set or {}," "),concat(more_set or {}," "))
326 end
327 local nofsolutions = #solutions + 1
328 local t = {
329 solution = solution,
330 less = less_set or { },
331 more = more_set or { },
332 settings = settings,
333 attribute = nofsolutions,
334 }
335 solutions[nofsolutions] = t
336 registered[name] = t
337 return nofsolutions
338end
339
340local nofwords, noftries, nofadapted, nofkept, nofparagraphs = 0, 0, 0, 0, 0
341
342local splitter_one = usernodeids["splitters.one"]
343local splitter_two = usernodeids["splitters.two"]
344
345local a_word = attributes.private('word')
346
347local encapsulate = false
348
349directives.register("builders.paragraphs.solutions.splitters.encapsulate", function(v)
350 encapsulate = v
351end)
352
353function splitters.split(head)
354 local current = head
355 local r2l = false
356 local start = nil
357 local stop = nil
358 local attribute = 0
359 cache = { }
360 max_less = 0
361 max_more = 0
362 local function flush()
363 local font = getfont(start)
364 local last = getnext(stop)
365 local list = last and copy_node_list(start,last) or copy_node_list(start)
366 local n = #cache + 1
367 if encapsulate then
368 local user_one = new_usernode(splitter_one,n)
369 local user_two = new_usernode(splitter_two,n)
370 head, start = insertnodebefore(head,start,user_one)
371 insertnodeafter(head,stop,user_two)
372 else
373 local current = start
374 while true do
375 setattr(current,a_word,n)
376 if current == stop then
377 break
378 else
379 current = getnext(current)
380 end
381 end
382 end
383 if r2l then
384 local dirnode = new_direction(righttoleft_code)
385 setlink(dirnode,list)
386 list = dirnode
387 end
388 local c = {
389 original = list,
390 attribute = attribute,
391
392 font = font
393 }
394 if trace_split then
395 report_splitters("cached %4i: font %a, attribute %a, direction %a, word %a",
396 n, font, attribute, nodes_to_utf(list,true), r2l and "r2l" or "l2r")
397 end
398 cache[n] = c
399 local solution = solutions[attribute]
400 local l = #solution.less
401 local m = #solution.more
402 if l > max_less then max_less = l end
403 if m > max_more then max_more = m end
404 start, stop = nil, nil
405 end
406 while current do
407 local next = getnext(current)
408 local id = getid(current)
409 if id == glyph_code then
410 if getsubtype(current) < 256 then
411 local a = getattr(current,a_split)
412 if not a then
413 start, stop = nil, nil
414 elseif not start then
415 start, stop, attribute = current, current, a
416 elseif a ~= attribute then
417 start, stop = nil, nil
418 else
419 stop = current
420 end
421 end
422 elseif id == disc_code then
423 if splitwords then
424 if start then
425 flush()
426 end
427 elseif start and next and getid(next) == glyph_code and getsubtype(next) < 256 then
428
429 stop = next
430 else
431 start, stop = nil, nil
432 end
433 elseif id == dir_code then
434
435 if start then
436 flush()
437 end
438 local direction, pop = getdirection(current)
439 r2l = not pop and direction == righttoleft_code
440 elseif id == par_code and startofpar(current) then
441 if start then
442 flush()
443 end
444 local direction = getdirection(current)
445 r2l = direction == righttoleft_code
446 else
447 if start then
448 flush()
449 end
450 end
451 current = next
452 end
453 if start then
454 flush()
455 end
456 nofparagraphs = nofparagraphs + 1
457 nofwords = nofwords + #cache
458 return head
459end
460
461local function collect_words(list)
462 local words = { }
463 local w = 0
464 local word = nil
465 if encapsulate then
466 for current, subtype in nextwhatsit, list do
467 if subtype == userdefinedwhatsit_code then
468 local p = nodeproperties[current]
469 if p then
470 local user_id = p.id
471 if user_id == splitter_one then
472 word = { p.data, current, current }
473 w = w + 1
474 words[w] = word
475 elseif user_id == splitter_two then
476 if word then
477 word[3] = current
478 else
479
480 end
481 end
482 end
483 end
484 end
485 else
486 local first, last, index
487 local current = list
488 while current do
489
490 local id = getid(current)
491 if id == glyph_code or id == disc_code then
492 local a = getattr(current,a_word)
493 if a then
494 if a == index then
495
496 last = current
497 elseif index then
498 w = w + 1
499 words[w] = { index, first, last }
500 first = current
501 last = current
502 index = a
503 elseif first then
504 last = current
505 index = a
506 else
507 first = current
508 last = current
509 index = a
510 end
511 elseif index then
512 if first then
513 w = w + 1
514 words[w] = { index, first, last }
515 end
516 index = nil
517 first = nil
518 elseif trace_split then
519 if id == disc_code then
520 report_splitters("skipped: disc node")
521 else
522 report_splitters("skipped: %C",current.char)
523 end
524 end
525 elseif id == kern_code and getsubtype(current) == fontkern_code then
526 if first then
527 last = current
528 else
529 first = current
530 last = current
531 end
532 elseif index then
533 w = w + 1
534 words[w] = { index, first, last }
535 index = nil
536 first = nil
537 if id == disc_code then
538 if trace_split then
539 report_splitters("skipped: disc node")
540 end
541 end
542 end
543 current = getnext(current)
544 end
545 if index then
546 w = w + 1
547 words[w] = { index, first, last }
548 end
549 if trace_split then
550 for i=1,#words do
551 local w = words[i]
552 local n = w[1]
553 local f = w[2]
554 local l = w[3]
555 local c = cache[n]
556 if c then
557 report_splitters("found %4i: word %a, cached %a",n,nodes_to_utf(f,true,true,l),nodes_to_utf(c.original,true))
558 else
559 report_splitters("found %4i: word %a, not in cache",n,nodes_to_utf(f,true,true,l))
560 end
561 end
562 end
563 end
564 return words, list
565end
566
567
568
569local function doit(word,list,best,width,badness,line,set,listdir)
570 local changed = 0
571 local n = word[1]
572 local found = cache[n]
573 if found then
574 local h, t
575 if encapsulate then
576 h = getnext(word[2])
577 t = getprev(word[3])
578 else
579 h = word[2]
580 t = word[3]
581 end
582 if splitwords then
583
584 else
585 local ok = false
586 local c = h
587 while c do
588 if c == t then
589 ok = true
590 break
591 else
592 c = getnext(c)
593 end
594 end
595 if not ok then
596 report_solutions("skipping hyphenated word (for now)")
597
598 return false, changed
599 end
600 end
601 local original = found.original
602 local attribute = found.attribute
603 local solution = solutions[attribute]
604 local features = solution and solution[set]
605 if features then
606 local featurenumber = features[best]
607 if featurenumber then
608 noftries = noftries + 1
609 local first = copy_node_list(original)
610 if not trace_colors then
611 for n in nextnode, first do
612 setattr(n,0,featurenumber)
613 end
614 elseif set == "less" then
615 for n in nextnode, first do
616 setnodecolor(n,"font:isol")
617 setattr(n,0,featurenumber)
618 end
619 else
620 for n in nextnode, first do
621 setnodecolor(n,"font:medi")
622 setattr(n,0,featurenumber)
623 end
624 end
625 local font = found.font
626 local setdynamics = setfontdynamics[font]
627 if setdynamics then
628 local processes = setdynamics[featurenumber]
629 for i=1,#processes do
630 first = processes[i](first,font,featurenumber)
631 end
632 else
633 report_solutions("fatal error, no dynamics for font %a",font)
634 end
635 first = inject_kerns(first)
636 if getid(first) == whatsit_code then
637 local temp = first
638 first = getnext(first)
639 flushnode(temp)
640 end
641 local last = find_node_tail(first)
642
643 local prev = getprev(h)
644 local next = getnext(t)
645 setlink(prev,first)
646 if next then
647 setlink(last,next)
648 end
649
650 local temp, b = repack_hlist(list,width,'exactly',listdir)
651 if b > badness then
652 if trace_optimize then
653 report_optimizers("line %a, set %a, badness before %a, after %a, criterium %a, verdict %a",line,set or "?",badness,b,criterium,"quit")
654 end
655
656 setlink(prev,h)
657 if next then
658 setlink(t,next)
659 else
660 setnext(t)
661 end
662 setnext(last)
663 flushnodelist(first)
664 else
665 if trace_optimize then
666 report_optimizers("line %a, set %a, badness before: %a, after %a, criterium %a, verdict %a",line,set or "?",badness,b,criterium,"continue")
667 end
668
669 setnext(t)
670 flushnodelist(h)
671 if not encapsulate then
672 word[2] = first
673 word[3] = last
674 end
675 changed, badness = changed + 1, b
676 end
677 if b <= criterium then
678 return true, changed
679 end
680 end
681 end
682 end
683 return false, changed
684end
685
686
687
688
689variants[v_normal] = function(words,list,best,width,badness,line,set,listdir)
690 local changed = 0
691 for i=1,#words do
692 local done, c = doit(words[i],list,best,width,badness,line,set,listdir)
693 changed = changed + c
694 if done then
695 break
696 end
697 end
698 if changed > 0 then
699 nofadapted = nofadapted + 1
700
701 local list, b = repack_hlist(list,width,'exactly',listdir)
702 return list, true, changed, b
703 else
704 nofkept = nofkept + 1
705 return list, false, 0, badness
706 end
707end
708
709variants[v_reverse] = function(words,list,best,width,badness,line,set,listdir)
710 local changed = 0
711 for i=#words,1,-1 do
712 local done, c = doit(words[i],list,best,width,badness,line,set,listdir)
713 changed = changed + c
714 if done then
715 break
716 end
717 end
718 if changed > 0 then
719 nofadapted = nofadapted + 1
720
721 local list, b = repack_hlist(list,width,'exactly',listdir)
722 return list, true, changed, b
723 else
724 nofkept = nofkept + 1
725 return list, false, 0, badness
726 end
727end
728
729variants[v_random] = function(words,list,best,width,badness,line,set,listdir)
730 local changed = 0
731 while #words > 0 do
732 local done, c = doit(remove(words,getrandom("solution",1,#words)),list,best,width,badness,line,set,listdir)
733 changed = changed + c
734 if done then
735 break
736 end
737 end
738 if changed > 0 then
739 nofadapted = nofadapted + 1
740
741 local list, b = repack_hlist(list,width,'exactly',listdir)
742 return list, true, changed, b
743 else
744 nofkept = nofkept + 1
745 return list, false, 0, badness
746 end
747end
748
749local function show_quality(current,what,line)
750 local set, order, sign = getboxglue(current)
751 local amount = set * ((sign == 2 and -1) or 1)
752 report_optimizers("line %a, category %a, amount %a, set %a, sign %a, how %a, order %a",line,what,amount,set,sign,how,order)
753end
754
755function splitters.optimize(head)
756 if not optimize then
757 report_optimizers("no optimizer set")
758 return
759 end
760 local nc = #cache
761 if nc == 0 then
762 return
763 end
764 starttiming(splitters)
765 local listdir = nil
766 if randomseed then
767 math.setrandomseedi(randomseed)
768 randomseed = nil
769 end
770 local line = 0
771 local tex_hbadness = tex.hbadness
772 local tex_hfuzz = tex.hfuzz
773 tex.hbadness = 10000
774 tex.hfuzz = number.maxdimen
775 if trace_optimize then
776 report_optimizers("preroll %a, variant %a, criterium %a, cache size %a",preroll,variant,criterium,nc)
777 end
778 for current in nexthlist, head do
779 line = line + 1
780 local sign = getfield(current,"glue_sign")
781 local direction = getdirection(current)
782 local width = getwidth(current)
783 local list = getlist(current)
784 if not encapsulate and getid(list) == glyph_code then
785
786
787 list = insertnodebefore(list,list,new_leftskip(0))
788 setlist(current,list)
789 end
790 local temp, badness = repack_hlist(list,width,"exactly",direction)
791 if badness > 0 then
792 if sign == 0 then
793 if trace_optimize then
794 report_optimizers("line %a, badness %a, outcome %a, verdict %a",line,badness,"okay","okay")
795 end
796 else
797 local set, max
798 if sign == 1 then
799 if trace_optimize then
800 report_optimizers("line %a, badness %a, outcome %a, verdict %a",line,badness,"underfull","trying more")
801 end
802 set, max = "more", max_more
803 else
804 if trace_optimize then
805 report_optimizers("line %a, badness %a, outcome %a, verdict %a",line,badness,"overfull","trying less")
806 end
807 set, max = "less", max_less
808 end
809
810 local lastbest = nil
811 local lastbadness = badness
812 if preroll then
813 local bb, base
814 for i=1,max do
815 if base then
816 flushnodelist(base)
817 end
818 base = copy_node_list(list)
819 local words = collect_words(base)
820 for j=i,max do
821 local temp, done, changes, b = optimize(words,base,j,width,badness,line,set,dir)
822 base = temp
823 if trace_optimize then
824 report_optimizers("line %a, alternative %a.%a, changes %a, badness %a",line,i,j,changes,b)
825 end
826 bb = b
827 if b <= criterium then
828 break
829 end
830
831
832
833 end
834 if bb and bb > criterium then
835 if not lastbest then
836 lastbest, lastbadness = i, bb
837 elseif bb > lastbadness then
838 lastbest, lastbadness = i, bb
839 end
840 else
841 break
842 end
843 end
844 flushnodelist(base)
845 end
846 local words = collect_words(list)
847 for best=lastbest or 1,max do
848 local temp, done, changes, b = optimize(words,list,best,width,badness,line,set,dir)
849 setlist(current,temp)
850 if trace_optimize then
851 report_optimizers("line %a, alternative %a, changes %a, badness %a",line,best,changes,b)
852 end
853 if done then
854 if b <= criterium then
855 protectglyphs(list)
856 break
857 end
858 end
859 end
860 end
861 else
862 if trace_optimize then
863 report_optimizers("line %a, verdict %a",line,"not bad enough")
864 end
865 end
866
867 local list = hpack_nodes(getlist(current),width,'exactly',listdir)
868 setlist(current,list)
869 end
870 for i=1,nc do
871 local ci = cache[i]
872 flushnodelist(ci.original)
873 end
874 cache = { }
875 tex.hbadness = tex_hbadness
876 tex.hfuzz = tex_hfuzz
877 stoptiming(splitters)
878end
879
880statistics.register("optimizer statistics", function()
881 if nofwords > 0 then
882 local elapsed = statistics.elapsedtime(splitters)
883 local average = noftries/elapsed
884 return format("%s words identified in %s paragraphs, %s words retried, %s lines tried, %s seconds used, %s adapted, %0.1f lines per second",
885 nofwords,nofparagraphs,noftries,nofadapted+nofkept,elapsed,nofadapted,average)
886 end
887end)
888
889
890
891local enableaction = tasks.enableaction
892local disableaction = tasks.disableaction
893
894local function enable()
895 enableaction("processors", "builders.paragraphs.solutions.splitters.split")
896 enableaction("finalizers", "builders.paragraphs.solutions.splitters.optimize")
897end
898
899local function disable()
900 disableaction("processors", "builders.paragraphs.solutions.splitters.split")
901 disableaction("finalizers", "builders.paragraphs.solutions.splitters.optimize")
902end
903
904function splitters.start(name,settings)
905 if pushsplitter(name,settings) == 1 then
906 enable()
907 end
908end
909
910function splitters.stop()
911 if popsplitter() == 0 then
912 disable()
913 end
914end
915
916function splitters.set(name,settings)
917 if #stack > 0 then
918 stack = { }
919 else
920 enable()
921 end
922 pushsplitter(name,settings)
923end
924
925function splitters.reset()
926 if #stack > 0 then
927 stack = { }
928 popsplitter()
929 disable()
930 end
931end
932
933
934
935implement {
936 name = "definefontsolution",
937 actions = splitters.define,
938 arguments = {
939 "string",
940 {
941 { "goodies" },
942 { "solution" },
943 { "less" },
944 { "more" },
945 }
946 }
947}
948
949implement {
950 name = "startfontsolution",
951 actions = splitters.start,
952 arguments = {
953 "string",
954 {
955 { "method" },
956 { "criterium" },
957 { "randomseed" },
958 }
959 }
960}
961
962implement {
963 name = "stopfontsolution",
964 actions = splitters.stop
965}
966
967implement {
968 name = "setfontsolution",
969 actions = splitters.set,
970 arguments = {
971 "string",
972 {
973 { "method" },
974 { "criterium" },
975 { "randomseed" },
976 }
977 }
978}
979
980implement {
981 name = "resetfontsolution",
982 actions = splitters.reset
983}
984 |