typo-dua.lua /size: 29 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['typo-dua'] = {
2    version   = 1.001,
3    comment   = "companion to typo-dir.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team / See below",
6    license   = "see context related readme files / whatever applies",
7    comment   = "Unicode bidi (sort of) variant a",
8    derived   = "derived from t-bidi by Khaled Hosny who derived from minibidi.c by Arabeyes",
9}
10
11-- Comment by Khaled Hosny:
12--
13-- This code started as a line for line translation of Arabeyes' minibidi.c from C to Lua,
14-- excluding parts that of no use to us like shaping. The C code is Copyright (c) 2004
15-- Ahmad Khalifa, and is distributed under the MIT Licence. The full license text can be
16-- found at: http://svn.arabeyes.org/viewvc/projects/adawat/minibidi/LICENCE.
17--
18-- Comment by Hans Hagen:
19--
20-- The initial conversion to Lua has been done by Khaled Hosny. As a first step I optimized the
21-- code (to suit todays context mkiv). Next I fixed the foreign object handling, for instance,
22-- we can skip over math but we need to inject before the open math node and after the close node,
23-- so we need to keep track of the endpoint. After I fixed that bit I realized that it was possible
24-- to generalize the object skipper if only because it saves memory (and processing time). The
25-- current implementation is about three times as fast (roughly measured) and I can probably squeeze
26-- out some more, only to sacrifice soem when I start adding features. A next stage will be to have
27-- more granularity in foreign objects. Of course all errors are mine. I'll also added the usual bit
28-- of context tracing and reshuffled some code. A memory optimization is on the agenda (already sort
29-- of prepared). It is no longer line by line.
30--
31-- The first implementation of bidi in context started out from examples of mixed usage (including
32-- more than text) with an at that point bugged r2l support. It has  some alternatives for letting
33-- the tex markup having a bit higher priority. I will  probably add some local (style driven)
34-- overrides to the following code as well. It also means that we can selectively enable and disable
35-- the parser (because a document wide appliance migh tnot be what we want). This will bring a
36-- slow down but not that much. (I need to check with Idris why we have things like isol there.)
37--
38-- We'll probably keep multiple methods around (this is just a side track of improving the already
39-- available scanner). I need to look into the changed unicode recomendations anyway as a first
40-- impression is that some fuzzyness has been removed. I finally need to spend time on those specs. So,
41-- there will be a third variant (written from scratch) so some point. The fun about TeX is that we
42-- can provide alternative solutions (given that it doesn't bloat the engine!)
43--
44-- A test with some hebrew, mixed with hboxes with latin/hebrew and simple math. In fact this triggered
45-- playing with bidi again:
46--
47-- 0.11 :      nothing
48-- 0.14 : 0.03 node list only, one pass
49-- 0.23 : 0.12 close to unicode bidi, multipass
50-- 0.44 : 0.33 original previous
51--
52-- todo: check for introduced errors
53-- todo: reuse list, we have size, so we can just change values (and auto allocate when not there)
54-- todo: reuse the stack
55-- todo: no need for a max check
56-- todo: collapse bound similar ranges (not ok yet)
57-- tood: combine some sweeps
58--
59-- This one wil get frozen (or if needed in sync with basic t-bidi) and I will explore more options
60-- in typo-dub.lua. There I might also be able to improve performance a bit. Derived and improved
61-- versions will also be sped up
62
63local insert, remove, unpack, concat = table.insert, table.remove, table.unpack, table.concat
64local utfchar = utf.char
65local formatters = string.formatters
66
67local directiondata       = characters.directions
68local mirrordata          = characters.mirrors
69
70local nuts                = nodes.nuts
71
72local getnext             = nuts.getnext
73local getid               = nuts.getid
74local getsubtype          = nuts.getsubtype
75local getlist             = nuts.getlist
76local getchar             = nuts.getchar
77local getprop             = nuts.getprop
78local getdirection        = nuts.getdirection
79
80local setprop             = nuts.setprop
81local setchar             = nuts.setchar
82local setdirection        = nuts.setdirection
83----- setattrlist         = nuts.setattrlist
84
85local remove_node         = nuts.remove
86local insertnodeafter     = nuts.insertafter
87local insertnodebefore    = nuts.insertbefore
88
89local startofpar          = nuts.startofpar
90
91local nodepool            = nuts.pool
92local new_direction       = nodepool.direction
93
94local nodecodes           = nodes.nodecodes
95local gluecodes           = nodes.gluecodes
96
97local glyph_code          = nodecodes.glyph
98local glue_code           = nodecodes.glue
99local hlist_code          = nodecodes.hlist
100local vlist_code          = nodecodes.vlist
101local math_code           = nodecodes.math
102local dir_code            = nodecodes.dir
103local par_code            = nodecodes.par
104
105local parfillskip_code    = gluecodes.parfillskip
106
107local dirvalues           = nodes.dirvalues
108local lefttoright_code    = dirvalues.lefttoright
109local righttoleft_code    = dirvalues.righttoleft
110
111local maximum_stack       = 60
112
113local directions          = typesetters.directions
114local setcolor            = directions.setcolor
115
116local remove_controls     = true  directives.register("typesetters.directions.one.removecontrols",function(v) remove_controls  = v end)
117
118local report_directions   = logs.reporter("typesetting","directions one")
119
120local trace_directions    = false trackers  .register("typesetters.directions",               function(v) trace_directions = v end)
121local trace_details       = false trackers  .register("typesetters.directions.details",       function(v) trace_details    = v end)
122
123
124
125
126local whitespace = {
127    lre = true,
128    rle = true,
129    lro = true,
130    rlo = true,
131    pdf = true,
132    bn  = true,
133    ws  = true,
134}
135
136local b_s_ws_on = {
137    b   = true,
138    s   = true,
139    ws  = true,
140    on  = true
141}
142
143-- tracing
144
145local function show_list(list,size,what)
146    local what   = what or "direction"
147    local joiner = utfchar(0x200C)
148    local result = { }
149    for i=1,size do
150        local entry     = list[i]
151        local character = entry.char
152        local direction = entry[what]
153        if character == 0xFFFC then
154            local first = entry.id
155            local last  = entry.last
156            local skip  = entry.skip
157            if last then
158                result[i] = formatters["%-3s:%s %s..%s (%i)"](direction,joiner,nodecodes[first],nodecodes[last],skip or 0)
159            else
160                result[i] = formatters["%-3s:%s %s (%i)"](direction,joiner,nodecodes[first],skip or 0)
161            end
162        elseif character >= 0x202A and character <= 0x202C then
163            result[i] = formatters["%-3s:%s %U"](direction,joiner,character)
164        else
165            result[i] = formatters["%-3s:%s %c %U"](direction,joiner,character,character)
166        end
167    end
168    return concat(result,joiner .. " | " .. joiner)
169end
170
171-- preparation
172
173local function show_done(list,size)
174    local joiner = utfchar(0x200C)
175    local result = { }
176    for i=1,size do
177        local entry     = list[i]
178        local character = entry.char
179        local begindir  = entry.begindir
180        local enddir    = entry.enddir
181        if begindir then
182            result[#result+1] = formatters["<%s>"](begindir)
183        end
184        if entry.remove then
185            -- continue
186        elseif character == 0xFFFC then
187            result[#result+1] = formatters["<%s>"]("?")
188        elseif character == 0x0020 then
189            result[#result+1] = formatters["<%s>"](" ")
190        elseif character >= 0x202A and character <= 0x202C then
191            result[#result+1] = formatters["<%s>"](entry.original)
192        else
193            result[#result+1] = utfchar(character)
194        end
195        if enddir then
196            result[#result+1] = formatters["<%s>"](enddir)
197        end
198    end
199    return concat(result,joiner)
200end
201
202-- keeping the list and overwriting doesn't save much runtime, only a few percent
203-- char is only used for mirror, so in fact we can as well only store it for
204-- glyphs only
205
206local function build_list(head) -- todo: store node pointer ... saves loop
207    -- P1
208    local current = head
209    local list    = { }
210    local size    = 0
211    while current do
212        size = size + 1
213        local id = getid(current)
214        if getprop(current,"directions") then
215            local skip = 0
216            local last = id
217            current    = getnext(current)
218            while current do
219                local id = getid(current)
220                if getprop(current,"directions") then
221                    skip    = skip + 1
222                    last    = id
223                    current = getnext(current)
224                else
225                    break
226                end
227            end
228            if id == last then -- the start id
229                list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id }
230            else
231                list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id, last = last }
232            end
233        elseif id == glyph_code then
234            local chr = getchar(current)
235            local dir = directiondata[chr]
236            list[size] = { char = chr, direction = dir, original = dir, level = 0 }
237            current = getnext(current)
238        elseif id == glue_code then -- and how about kern
239            list[size] = { char = 0x0020, direction = "ws", original = "ws", level = 0 }
240            current = getnext(current)
241        elseif id == dir_code then
242            local direction, pop = getdirection(current)
243            if direction == lefttoright_code then
244                if pop then
245                    list[size] = { char = 0x202C, direction = "pdf", original = "pdf", level = 0 }
246                else
247                    list[size] = { char = 0x202A, direction = "lre", original = "lre", level = 0 }
248                end
249            elseif direction == righttoleft_code then
250                if pop then
251                    list[size] = { char = 0x202C, direction = "pdf", original = "pdf", level = 0 }
252                else
253                    list[size] = { char = 0x202B, direction = "rle", original = "rle", level = 0 }
254                end
255            else
256                list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, id = id } -- object replacement character
257            end
258            current = getnext(current)
259        elseif id == math_code then
260            local skip = 0
261            current    = getnext(current)
262            while getid(current) ~= math_code do
263                skip    = skip + 1
264                current = getnext(current)
265            end
266            skip       = skip + 1
267            current    = getnext(current)
268            list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id }
269        else
270            local skip = 0
271            local last = id
272            current    = getnext(current)
273            while n do
274                local id = getid(current)
275                if id ~= glyph_code and id ~= glue_code and id ~= dir_code then
276                    skip    = skip + 1
277                    last    = id
278                    current = getnext(current)
279                else
280                    break
281                end
282            end
283            if id == last then -- the start id
284                list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id }
285            else
286                list[size] = { char = 0xFFFC, direction = "on", original = "on", level = 0, skip = skip, id = id, last = last }
287            end
288        end
289    end
290    return list, size
291end
292
293-- the action
294
295-- local function find_run_limit_et(list,run_start,limit)
296--     local run_limit = run_start
297--     local i = run_start
298--     while i <= limit and list[i].direction == "et" do
299--         run_limit = i
300--         i = i + 1
301--     end
302--     return run_limit
303-- end
304
305local function find_run_limit_et(list,start,limit) -- returns last match
306    for i=start,limit do
307        if list[i].direction == "et" then
308            start = i
309        else
310            return start
311        end
312    end
313    return start
314end
315
316-- local function find_run_limit_b_s_ws_on(list,run_start,limit)
317--     local run_limit = run_start
318--     local i = run_start
319--     while i <= limit and b_s_ws_on[list[i].direction] do
320--         run_limit = i
321--         i = i + 1
322--     end
323--     return run_limit
324-- end
325
326local function find_run_limit_b_s_ws_on(list,start,limit)
327    for i=start,limit do
328        if b_s_ws_on[list[i].direction] then
329            start = i
330        else
331            return start
332        end
333    end
334    return start
335end
336
337local function get_baselevel(head,list,size,direction)
338    -- This is an adapted version:
339    if direction == lefttoright_code or direction == righttoleft_code then
340        return direction, true
341    elseif getid(head) == par_code and startofpar(head) then
342        direction = getdirection(head)
343        if direction == lefttoright_code or direction == righttoleft_code then
344            return direction, true
345        end
346    end
347    -- P2, P3
348    for i=1,size do
349        local entry     = list[i]
350        local direction = entry.direction
351        if direction == "r" or direction == "al" then
352            return righttoleft_code, true
353        elseif direction == "l" then
354            return lefttoright_code, true
355        end
356    end
357    return lefttoright_code, false
358end
359
360local function resolve_explicit(list,size,baselevel)
361    -- X1
362    local level    = baselevel
363    local override = "on"
364    local stack    = { }
365    local nofstack = 0
366    for i=1,size do
367        local entry     = list[i]
368        local direction = entry.direction
369        -- X2
370        if direction == "rle" then
371            if nofstack < maximum_stack then
372                nofstack        = nofstack + 1
373                stack[nofstack] = { level, override }
374                level           = level + (level % 2 == 1 and 2 or 1) -- least_greater_odd(level)
375                override        = "on"
376                entry.level     = level
377                entry.direction = "bn"
378                entry.remove    = true
379            elseif trace_directions then
380                report_directions("stack overflow at position %a with direction %a",i,direction)
381            end
382        -- X3
383        elseif direction == "lre" then
384            if nofstack < maximum_stack then
385                nofstack        = nofstack + 1
386                stack[nofstack] = { level, override }
387                level           = level + (level % 2 == 1 and 1 or 2) -- least_greater_even(level)
388                override        = "on"
389                entry.level     = level
390                entry.direction = "bn"
391                entry.remove    = true
392            elseif trace_directions then
393                report_directions("stack overflow at position %a with direction %a",i,direction)
394            end
395        -- X4
396        elseif direction == "rlo" then
397            if nofstack < maximum_stack then
398                nofstack        = nofstack + 1
399                stack[nofstack] = { level, override }
400                level           = level + (level % 2 == 1 and 2 or 1) -- least_greater_odd(level)
401                override        = "r"
402                entry.level     = level
403                entry.direction = "bn"
404                entry.remove    = true
405            elseif trace_directions then
406                report_directions("stack overflow at position %a with direction %a",i,direction)
407            end
408        -- X5
409        elseif direction == "lro" then
410            if nofstack < maximum_stack then
411                nofstack        = nofstack + 1
412                stack[nofstack] = { level, override }
413                level           = level + (level % 2 == 1 and 1 or 2) -- least_greater_even(level)
414                override        = "l"
415                entry.level     = level
416                entry.direction = "bn"
417                entry.remove    = true
418            elseif trace_directions then
419                report_directions("stack overflow at position %a with direction %a",i,direction)
420            end
421        -- X7
422        elseif direction == "pdf" then
423            if noifstack > 0 then
424                local stacktop  = stack[nofstack]
425                nofstack        = nofstack - 1
426                level           = stacktop[1]
427                override        = stacktop[2]
428                entry.level     = level
429                entry.direction = "bn"
430                entry.remove    = true
431            elseif trace_directions then
432                report_directions("stack underflow at position %a with direction %a",i,direction)
433            end
434        -- X6
435        else
436            entry.level = level
437            if override ~= "on" then
438                entry.direction = override
439            end
440        end
441    end
442    -- X8 (reset states and overrides after paragraph)
443end
444
445local function resolve_weak(list,size,start,limit,sor,eor)
446    -- W1
447    for i=start,limit do
448        local entry = list[i]
449        if entry.direction == "nsm" then
450            if i == start then
451                entry.direction = sor
452            else
453                entry.direction = list[i-1].direction
454            end
455        end
456    end
457    -- W2
458    for i=start,limit do
459        local entry = list[i]
460        if entry.direction == "en" then
461            for j=i-1,start,-1 do
462                local prev = list[j]
463                local direction = prev.direction
464                if direction == "al" then
465                    entry.direction = "an"
466                    break
467                elseif direction == "r" or direction == "l" then
468                    break
469                end
470            end
471        end
472    end
473    -- W3
474    for i=start,limit do
475        local entry = list[i]
476        if entry.direction == "al" then
477            entry.direction = "r"
478        end
479    end
480    -- W4
481    for i=start+1,limit-1 do
482        local entry     = list[i]
483        local direction = entry.direction
484        if direction == "es" then
485            if list[i-1].direction == "en" and list[i+1].direction == "en" then
486                entry.direction = "en"
487            end
488        elseif direction == "cs" then
489            local prevdirection = list[i-1].direction
490            if prevdirection == "en" then
491                if list[i+1].direction == "en" then
492                    entry.direction = "en"
493                end
494            elseif prevdirection == "an" and list[i+1].direction == "an" then
495                entry.direction = "an"
496            end
497        end
498    end
499    -- W5
500    local i = start
501    while i <= limit do
502        if list[i].direction == "et" then
503            local runstart     = i
504            local runlimit     = find_run_limit_et(list,runstart,limit) -- when moved inline we can probably collapse a lot
505            local rundirection = runstart == start and sor or list[runstart-1].direction
506            if rundirection ~= "en" then
507                rundirection = runlimit == limit and eor or list[runlimit+1].direction
508            end
509            if rundirection == "en" then
510                for j=runstart,runlimit do
511                    list[j].direction = "en"
512                end
513            end
514            i = runlimit
515        end
516        i = i + 1
517    end
518    -- W6
519    for i=start,limit do
520        local entry     = list[i]
521        local direction = entry.direction
522        if direction == "es" or direction == "et" or direction == "cs" then
523            entry.direction = "on"
524        end
525    end
526    -- W7
527    for i=start,limit do
528        local entry = list[i]
529        if entry.direction == "en" then
530            local prev_strong = sor
531            for j=i-1,start,-1 do
532                local direction = list[j].direction
533                if direction == "l" or direction == "r" then
534                    prev_strong = direction
535                    break
536                end
537            end
538            if prev_strong == "l" then
539                entry.direction = "l"
540            end
541        end
542    end
543end
544
545local function resolve_neutral(list,size,start,limit,sor,eor)
546    -- N1, N2
547    for i=start,limit do
548        local entry = list[i]
549        if b_s_ws_on[entry.direction] then
550            local leading_direction, trailing_direction, resolved_direction
551            local runstart = i
552            local runlimit = find_run_limit_b_s_ws_on(list,runstart,limit)
553            if runstart == start then
554                leading_direction = sor
555            else
556                leading_direction = list[runstart-1].direction
557                if leading_direction == "en" or leading_direction == "an" then
558                    leading_direction = "r"
559                end
560            end
561            if runlimit == limit then
562                trailing_direction = eor
563            else
564                trailing_direction = list[runlimit+1].direction
565                if trailing_direction == "en" or trailing_direction == "an" then
566                    trailing_direction = "r"
567                end
568            end
569            if leading_direction == trailing_direction then
570                -- N1
571                resolved_direction = leading_direction
572            else
573                -- N2 / does the weird period
574                resolved_direction = entry.level % 2 == 1 and "r" or "l" -- direction_of_level(entry.level)
575            end
576            for j=runstart,runlimit do
577                list[j].direction = resolved_direction
578            end
579            i = runlimit
580        end
581        i = i + 1
582    end
583end
584
585-- local function resolve_implicit(list,size,start,limit,sor,eor)
586--     -- I1
587--     for i=start,limit do
588--         local entry = list[i]
589--         local level = entry.level
590--         if level % 2 ~= 1 then -- not odd(level)
591--             local direction = entry.direction
592--             if direction == "r" then
593--                 entry.level = level + 1
594--             elseif direction == "an" or direction == "en" then
595--                 entry.level = level + 2
596--             end
597--         end
598--     end
599--     -- I2
600--     for i=start,limit do
601--         local entry = list[i]
602--         local level = entry.level
603--         if level % 2 == 1 then -- odd(level)
604--             local direction = entry.direction
605--             if direction == "l" or direction == "en" or direction == "an" then
606--                 entry.level = level + 1
607--             end
608--         end
609--     end
610-- end
611
612local function resolve_implicit(list,size,start,limit,sor,eor)
613    for i=start,limit do
614        local entry     = list[i]
615        local level     = entry.level
616        local direction = entry.direction
617        if level % 2 ~= 1 then -- even
618            -- I1
619            if direction == "r" then
620                entry.level = level + 1
621            elseif direction == "an" or direction == "en" then
622                entry.level = level + 2
623            end
624        else
625            -- I2
626            if direction == "l" or direction == "en" or direction == "an" then
627                entry.level = level + 1
628            end
629        end
630    end
631end
632
633local function resolve_levels(list,size,baselevel)
634    -- X10
635    local start = 1
636    while start < size do
637        local level = list[start].level
638        local limit = start + 1
639        while limit < size and list[limit].level == level do
640            limit = limit + 1
641        end
642        local prev_level = start == 1    and baselevel or list[start-1].level
643        local next_level = limit == size and baselevel or list[limit+1].level
644        local sor = (level > prev_level and level or prev_level) % 2 == 1 and "r" or "l" -- direction_of_level(max(level,prev_level))
645        local eor = (level > next_level and level or next_level) % 2 == 1 and "r" or "l" -- direction_of_level(max(level,next_level))
646        -- W1 .. W7
647        resolve_weak(list,size,start,limit,sor,eor)
648        -- N1 .. N2
649        resolve_neutral(list,size,start,limit,sor,eor)
650        -- I1 .. I2
651        resolve_implicit(list,size,start,limit,sor,eor)
652        start = limit
653    end
654    -- L1
655    for i=1,size do
656        local entry     = list[i]
657        local direction = entry.original
658        -- (1)
659        if direction == "s" or direction == "b" then
660            entry.level = baselevel
661            -- (2)
662            for j=i-1,1,-1 do
663                local entry = list[j]
664                if whitespace[entry.original] then
665                    entry.level = baselevel
666                else
667                    break
668                end
669            end
670        end
671    end
672    -- (3)
673    for i=size,1,-1 do
674        local entry = list[i]
675        if whitespace[entry.original] then
676            entry.level = baselevel
677        else
678            break
679        end
680    end
681    -- L4
682    for i=1,size do
683        local entry = list[i]
684        if entry.level % 2 == 1 then -- odd(entry.level)
685            local mirror = mirrordata[entry.char]
686            if mirror then
687                entry.mirror = mirror
688            end
689        end
690    end
691end
692
693local function insert_dir_points(list,size)
694    -- L2, but no actual reversion is done, we simply annotate where
695    -- begindir/endddir node will be inserted.
696    local maxlevel = 0
697    local finaldir = false
698    for i=1,size do
699        local level = list[i].level
700        if level > maxlevel then
701            maxlevel = level
702        end
703    end
704    for level=0,maxlevel do
705        local started  = false
706        local begindir = nil
707        local enddir   = nil
708        if level % 2 == 1 then
709            begindir = righttoleft_code
710            enddir   = righttoleft_code
711        else
712            begindir = lefttoright_code
713            enddir   = lefttoright_code
714        end
715        for i=1,size do
716            local entry = list[i]
717            if entry.level >= level then
718                if not started then
719                    entry.begindir = begindir
720                    started        = true
721                end
722            else
723                if started then
724                    list[i-1].enddir = enddir
725                    started          = false
726                end
727            end
728        end
729        -- make sure to close the run at end of line
730        if started then
731            finaldir = enddir
732        end
733    end
734    if finaldir then
735        list[size].enddir = finaldir
736    end
737end
738
739local function apply_to_list(list,size,head,pardir)
740    local index   = 1
741    local current = head
742    while current do
743        if index > size then
744            report_directions("fatal error, size mismatch")
745            break
746        end
747        local id       = getid(current)
748        local entry    = list[index]
749        local begindir = entry.begindir
750        local enddir   = entry.enddir
751        setprop(current,"directions",true)
752        if id == glyph_code then
753            local mirror = entry.mirror
754            if mirror then
755                setchar(current,mirror)
756            end
757            if trace_directions then
758                local direction = entry.direction
759                setcolor(current,direction,false,mirror)
760            end
761        elseif id == hlist_code or id == vlist_code then
762            setdirection(current,pardir) -- is this really needed?
763        elseif id == glue_code then
764            if enddir and getsubtype(current) == parfillskip_code then
765                -- insert the last enddir before \parfillskip glue
766                local d = new_direction(enddir,true)
767             -- setprop(d,"directions",true)
768             -- setattrlist(d,current)
769                head = insertnodebefore(head,current,d)
770                enddir = false
771            end
772        elseif begindir then
773            if id == par_code and startofpar(current) then
774                -- par should always be the 1st node
775                local d = new_direction(begindir)
776             -- setprop(d,"directions",true)
777             -- setattrlist(d,current)
778                head, current = insertnodeafter(head,current,d)
779                begindir = nil
780            end
781        end
782        if begindir then
783            local d = new_direction(begindir)
784         -- setprop(d,"directions",true)
785         -- setattrlist(d,current)
786            head = insertnodebefore(head,current,d)
787        end
788        local skip = entry.skip
789        if skip and skip > 0 then
790            for i=1,skip do
791                current = getnext(current)
792                setprop(current,"directions",true)
793            end
794        end
795        if enddir then
796            local d = new_direction(enddir,true)
797         -- setprop(d,"directions",true)
798         -- setattrlist(d,current)
799            head, current = insertnodeafter(head,current,d)
800        end
801        if not entry.remove then
802            current = getnext(current)
803        elseif remove_controls then
804            -- X9
805            head, current = remove_node(head,current,true)
806        else
807            current = getnext(current)
808        end
809        index = index + 1
810    end
811    return head
812end
813
814local function process(head,direction,only_one,where)
815    -- This is an adapted version:
816    local list, size = build_list(head)
817    local baselevel, dirfound = get_baselevel(head,list,size,direction)
818    if not dirfound and trace_details then
819        report_directions("no initial direction found, gambling")
820    end
821    if trace_details then
822        report_directions("before : %s",show_list(list,size,"original"))
823    end
824    resolve_explicit(list,size,baselevel)
825    resolve_levels(list,size,baselevel)
826    insert_dir_points(list,size)
827    if trace_details then
828        report_directions("after  : %s",show_list(list,size,"direction"))
829        report_directions("result : %s",show_done(list,size))
830    end
831    return apply_to_list(list,size,head,baselevel)
832end
833
834directions.installhandler(interfaces.variables.one,process)
835