typo-dha.lua /size: 17 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['typo-dha'] = {
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",
6    license   = "see context related readme files"
7}
8
9-- Some analysis by Idris:
10--
11-- 1. Assuming the reading- vs word-order distinction (bidi-char types) is governing;
12-- 2. Assuming that 'ARAB' represents an actual arabic string in raw input order, not word-order;
13-- 3. Assuming that 'BARA' represent the correct RL word order;
14--
15-- Then we have, with input: LATIN ARAB
16--
17-- \textdirection 1 LATIN ARAB => LATIN BARA
18-- \textdirection 1 LATIN ARAB => LATIN BARA
19-- \textdirection 1 LRO LATIN ARAB => LATIN ARAB
20-- \textdirection 1 LRO LATIN ARAB => LATIN ARAB
21-- \textdirection 1 RLO LATIN ARAB => NITAL ARAB
22-- \textdirection 1 RLO LATIN ARAB => NITAL ARAB
23
24-- elseif d == "es"  then -- European Number Separator
25-- elseif d == "et"  then -- European Number Terminator
26-- elseif d == "cs"  then -- Common Number Separator
27-- elseif d == "nsm" then -- Non-Spacing Mark
28-- elseif d == "bn"  then -- Boundary Neutral
29-- elseif d == "b"   then -- Paragraph Separator
30-- elseif d == "s"   then -- Segment Separator
31-- elseif d == "ws"  then -- Whitespace
32-- elseif d == "on"  then -- Other Neutrals
33
34-- todo  : use new dir functions
35-- todo  : make faster
36-- todo  : move dir info into nodes
37-- todo  : swappable tables and floats i.e. start-end overloads (probably loop in builders)
38
39-- I removed the original tracing code and now use the colorful one. If I ever want to change
40-- something I will just inject prints for tracing.
41
42local nodes, node = nodes, node
43
44local trace_directions   = false  trackers.register("typesetters.directions", function(v) trace_directions = v end)
45
46local report_directions  = logs.reporter("typesetting","text directions")
47
48local nuts               = nodes.nuts
49
50local getnext            = nuts.getnext
51local getprev            = nuts.getprev
52local getchar            = nuts.getchar
53local getid              = nuts.getid
54local getsubtype         = nuts.getsubtype
55local getlist            = nuts.getlist
56local getattr            = nuts.getattr
57local getprop            = nuts.getprop
58local getdirection       = nuts.getdirection
59local isglyph            = nuts.isglyph -- or ischar
60
61local setprop            = nuts.setprop
62local setstate           = nuts.setstate
63local setchar            = nuts.setchar
64
65local insertnodebefore   = nuts.insertbefore
66local insertnodeafter    = nuts.insertafter
67local remove_node        = nuts.remove
68local endofmath          = nuts.endofmath
69
70local startofpar         = nuts.startofpar
71
72local nodepool           = nuts.pool
73
74local nodecodes          = nodes.nodecodes
75local gluecodes          = nodes.gluecodes
76
77local glyph_code         = nodecodes.glyph
78local math_code          = nodecodes.math
79local kern_code          = nodecodes.kern
80local glue_code          = nodecodes.glue
81local dir_code           = nodecodes.dir
82local par_code           = nodecodes.par
83
84local dirvalues          = nodes.dirvalues
85local lefttoright_code   = dirvalues.lefttoright
86local righttoleft_code   = dirvalues.righttoleft
87
88local parfillskip_code   = gluecodes.parfillskip
89
90local new_direction      = nodepool.direction
91
92local insert             = table.insert
93
94local fonthashes         = fonts.hashes
95local fontchar           = fonthashes.characters
96
97local chardirections     = characters.directions
98local charmirrors        = characters.mirrors
99local charclasses        = characters.textclasses
100
101local directions         = typesetters.directions
102local setcolor           = directions.setcolor
103local getglobal          = directions.getglobal
104
105local a_directions       = attributes.private('directions')
106
107local strip              = false
108
109local s_isol             = fonts.analyzers.states.isol
110
111local function stopdir(finish) -- we could use finish directly
112    local n = new_direction(finish == righttoleft_code and righttoleft_code or lefttoright_code,true)
113    setprop(n,"direction",true)
114    return n
115end
116
117local function startdir(finish) -- we could use finish directly
118    local n = new_direction(finish == righttoleft_code and righttoleft_code or lefttoright_code)
119    setprop(n,"direction",true)
120    return n
121end
122
123local function nextisright(current)
124    current = getnext(current)
125    local character, id = isglyph(current)
126    if character then
127        local direction = chardirections[character]
128        return direction == "r" or direction == "al" or direction == "an"
129    end
130end
131
132local function previsright(current)
133    current = getprev(current)
134    local character, id = isglyph(current)
135    if character then
136        local direction = chardirections[character]
137        return direction == "r" or direction == "al" or direction == "an"
138    end
139end
140
141local function process(start)
142
143    local head     = start
144    local current  = head
145    local autodir  = 0
146    local embedded = 0
147    local override = 0
148    local pardir   = 0
149    local textdir  = 0
150    local done     = false
151    local stack    = { }
152    local top      = 0
153    local obsolete = { }
154    local rlo      = false
155    local lro      = false
156    local prevattr = false
157    local fences   = { }
158
159    while current do
160        -- no isglyph here as we test for skips first
161        local id   = getid(current)
162        local next = getnext(current)
163        if id == math_code then
164            current = getnext(endofmath(next))
165        elseif getprop(current,"direction") then
166            -- this handles unhbox etc
167            current = next
168        else
169            local attr = getattr(current,a_directions)
170            if attr and attr > 0 then
171                if attr ~= prevattr then
172                    if not getglobal(a) then
173                        lro = false
174                        rlo = false
175                    end
176                    prevattr = attr
177                end
178            end
179            if id == glyph_code then
180                if attr and attr > 0 then
181                    local character, font = isglyph(current)
182                    if character == 0 then
183                        -- skip signals
184                        setprop(current,"direction",true)
185                    else
186                        local direction = chardirections[character]
187                        local reversed  = false
188                        if rlo or override > 0 then
189                            if direction == "l" then
190                                direction = "r"
191                                reversed  = true
192                            end
193                        elseif lro or override < 0 then
194                            if direction == "r" or direction == "al" then
195                                setstate(current,s_isol) -- hm
196                                direction = "l"
197                                reversed  = true
198                            end
199                        end
200                        if direction == "on" then
201                            local mirror = charmirrors[character]
202                            if mirror and fontchar[font][mirror] then
203                                local class = charclasses[character]
204                                if class == "open" then
205                                    if nextisright(current) then
206                                        setchar(current,mirror)
207                                        setprop(current,"direction","r")
208                                    elseif autodir < 0 then
209                                        setchar(current,mirror)
210                                        setprop(current,"direction","r")
211                                    else
212                                        mirror = false
213                                        setprop(current,"direction","l")
214                                    end
215                                    local fencedir = autodir == 0 and textdir or autodir
216                                    fences[#fences+1] = fencedir
217                                elseif class == "close" and #fences > 0 then
218                                    local fencedir = fences[#fences]
219                                    fences[#fences] = nil
220                                    if fencedir < 0 then
221                                        setchar(current,mirror)
222                                        setprop(current,"direction","r")
223                                    else
224                                        setprop(current,"direction","l")
225                                        mirror = false
226                                    end
227                                elseif autodir < 0 then
228                                    setchar(current,mirror)
229                                    setprop(current,"direction","r")
230                                else
231                                    setprop(current,"direction","l")
232                                    mirror = false
233                                end
234                            else
235                                setprop(current,"direction",true)
236                            end
237                            if trace_directions then
238                                setcolor(current,direction,false,mirror)
239                            end
240                        elseif direction == "l" then
241                            if trace_directions then
242                                setcolor(current,"l",reversed)
243                            end
244                            setprop(current,"direction","l")
245                        elseif direction == "r" then
246                            if trace_directions then
247                                setcolor(current,"r",reversed)
248                            end
249                            setprop(current,"direction","r")
250                        elseif direction == "en" then -- european number
251                            if trace_directions then
252                                setcolor(current,"l")
253                            end
254                            setprop(current,"direction","l")
255                        elseif direction == "al" then -- arabic letter
256                            if trace_directions then
257                                setcolor(current,"r")
258                            end
259                            setprop(current,"direction","r")
260                        elseif direction == "an" then -- arabic number
261                            -- needs a better scanner as it can be a float
262                            if trace_directions then
263                                setcolor(current,"l") -- was r
264                            end
265                            setprop(current,"direction","n") -- was r
266                        elseif direction == "lro" then -- Left-to-Right Override -> right becomes left
267                            top        = top + 1
268                            stack[top] = { override, embedded }
269                            override   = -1
270                            obsolete[#obsolete+1] = current
271                        elseif direction == "rlo" then -- Right-to-Left Override -> left becomes right
272                            top        = top + 1
273                            stack[top] = { override, embedded }
274                            override   = 1
275                            obsolete[#obsolete+1] = current
276                        elseif direction == "lre" then -- Left-to-Right Embedding -> lefttoright_code
277                            top        = top + 1
278                            stack[top] = { override, embedded }
279                            embedded   = 1
280                            obsolete[#obsolete+1] = current
281                        elseif direction == "rle" then -- Right-to-Left Embedding -> righttoleft_code
282                            top        = top + 1
283                            stack[top] = { override, embedded }
284                            embedded   = -1
285                            obsolete[#obsolete+1] = current
286                        elseif direction == "pdf" then -- Pop Directional Format
287                            if top > 0 then
288                                local s  = stack[top]
289                                override = s[1]
290                                embedded = s[2]
291                                top      = top - 1
292                            else
293                                override = 0
294                                embedded = 0
295                            end
296                            obsolete[#obsolete+1] = current
297                        elseif trace_directions then
298                            setcolor(current)
299                            setprop(current,"direction",true)
300                        else
301                            setprop(current,"direction",true)
302                        end
303                    end
304                else
305                    setprop(current,"direction",true)
306                end
307            elseif id == glue_code then
308                if getsubtype(current) == parfillskip_code then
309                    setprop(current,"direction",'!')
310                else
311                    setprop(current,"direction",'g')
312                end
313            elseif id == kern_code then
314                setprop(current,"direction",'k')
315            elseif id == dir_code then
316                local direction, pop = getdirection(current)
317                if direction == righttoleft_code then
318                    if not pop then
319                        autodir = -1
320                    elseif embedded and embedded~= 0 then
321                        autodir = embedded
322                    else
323                        autodir = 0
324                    end
325                elseif direction == lefttoright_code then
326                    if not pop then
327                        autodir = 1
328                    elseif embedded and embedded~= 0 then
329                        autodir = embedded
330                    else
331                        autodir = 0
332                    end
333                end
334                textdir = autodir
335                setprop(current,"direction",true)
336            elseif id == par_code and startofpar(current) then
337                local direction = getdirection(current)
338                if direction == righttoleft_code then
339                    autodir = -1
340                elseif direction == lefttoright_code then
341                    autodir = 1
342                end
343                pardir  = autodir
344                textdir = pardir
345                setprop(current,"direction",true)
346            else
347                setprop(current,"direction",true)
348            end
349            current = next
350        end
351    end
352
353    -- todo: track if really needed
354    -- todo: maybe we need to set the property (as it can be a copied list)
355
356    if done and strip then
357        local n = #obsolete
358        if n > 0 then
359            for i=1,n do
360                remove_node(head,obsolete[i],true)
361            end
362            if trace_directions then
363                report_directions("%s character nodes removed",n)
364            end
365        end
366    end
367
368    local state    = false
369    local last     = false
370    local collapse = true
371    current        = head
372
373    -- todo: textdir
374    -- todo: inject before parfillskip
375
376    while current do
377        local id = getid(current)
378        if id == math_code then
379            -- todo: this might be tricky nesting
380            current = getnext(endofmath(getnext(current)))
381        else
382            local cp = getprop(current,"direction")
383            if cp == "n" then
384                local swap = state == "r"
385                if swap then
386                    head = insertnodebefore(head,current,startdir(lefttoright_code))
387                end
388                setprop(current,"direction",true)
389                while true do
390                    local n = getnext(current)
391                    if n and getprop(n,"direction") == "n" then
392                        current = n
393                        setprop(current,"direction",true)
394                    else
395                        break
396                    end
397                end
398                if swap then
399                    head, current = insertnodeafter(head,current,stopdir(lefttoright_code))
400                end
401            elseif cp == "l" then
402                if state ~= "l" then
403                    if state == "r" then
404                        head = insertnodebefore(head,last or current,stopdir(righttoleft_code))
405                    end
406                    head  = insertnodebefore(head,current,startdir(lefttoright_code))
407                    state = "l"
408                    done  = true
409                end
410                last  = false
411            elseif cp == "r" then
412                if state ~= "r" then
413                    if state == "l" then
414                        head = insertnodebefore(head,last or current,stopdir(lefttoright_code))
415                    end
416                    head  = insertnodebefore(head,current,startdir(righttoleft_code))
417                    state = "r"
418                    done  = true
419                end
420                last = false
421            elseif collapse then
422                if cp == "k" or cp == "g" then
423                    last = last or current
424                else
425                    last = false
426                end
427            else
428                if state == "r" then
429                    head = insertnodebefore(head,current,stopdir(righttoleft_code))
430                elseif state == "l" then
431                    head = insertnodebefore(head,current,stopdir(lefttoright_code))
432                end
433                state = false
434                last  = false
435            end
436            setprop(current,"direction",true)
437        end
438        local next = getnext(current)
439        if next then
440            current = next
441        else
442            local sd = (state == "r" and stopdir(righttoleft_code)) or (state == "l" and stopdir(lefttoright_code))
443            if sd then
444                if id == glue_code and getsubtype(current) == parfillskip_code then
445                    head = insertnodebefore(head,current,sd)
446                else
447                    head = insertnodeafter(head,current,sd)
448                end
449            end
450            break
451        end
452    end
453
454    return head
455
456end
457
458directions.installhandler(interfaces.variables.default,process)
459