typo-dha.lmt /size: 18 Kb    last modification: 2024-01-16 10:22
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 parfillskip_code   = gluecodes.parfillskip
85
86local directioncodes     = tex.directioncodes
87local lefttoright_code   = directioncodes.lefttoright
88local righttoleft_code   = directioncodes.righttoleft
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            local prop = true
180            if id == glyph_code then
181                if attr and attr > 0 then
182                    local character, font = isglyph(current)
183                    if character == 0 then
184                        -- skip signals
185                     -- setprop(current,"direction",true)
186                    else
187                        local direction = chardirections[character]
188                        local reversed  = false
189                        if rlo or override > 0 then
190                            if direction == "l" then
191                                direction = "r"
192                                reversed  = true
193                            end
194                        elseif lro or override < 0 then
195                            if direction == "r" or direction == "al" then
196                                setstate(current,s_isol) -- hm
197                                direction = "l"
198                                reversed  = true
199                            end
200                        end
201                        if direction == "on" then
202                            local mirror = charmirrors[character]
203                            if mirror and fontchar[font][mirror] then
204                                local class = charclasses[character]
205                                if class == "open" then
206                                    if nextisright(current) then
207                                        setchar(current,mirror)
208                                     -- setprop(current,"direction","r")
209                                        prop = "r"
210                                    elseif autodir < 0 then
211                                        setchar(current,mirror)
212                                     -- setprop(current,"direction","r")
213                                        prop = "r"
214                                    else
215                                        mirror = false
216                                     -- setprop(current,"direction","l")
217                                        prop = "l"
218                                    end
219                                    local fencedir = autodir == 0 and textdir or autodir
220                                    fences[#fences+1] = fencedir
221                                elseif class == "close" and #fences > 0 then
222                                    local fencedir = fences[#fences]
223                                    fences[#fences] = nil
224                                    if fencedir < 0 then
225                                        setchar(current,mirror)
226                                     -- setprop(current,"direction","r")
227                                        prop = "r"
228                                    else
229                                     -- setprop(current,"direction","l")
230                                        prop = "l"
231                                        mirror = false
232                                    end
233                                elseif autodir < 0 then
234                                    setchar(current,mirror)
235                                 -- setprop(current,"direction","r")
236                                    prop = "r"
237                                else
238                                 -- setprop(current,"direction","l")
239                                    prop = "l"
240                                    mirror = false
241                                end
242                            else
243                             -- setprop(current,"direction",true)
244                            end
245                            if trace_directions then
246                                setcolor(current,direction,false,mirror)
247                            end
248                        elseif direction == "l" then
249                            if trace_directions then
250                                setcolor(current,"l",reversed)
251                            end
252                         -- setprop(current,"direction","l")
253                            prop = "l"
254                        elseif direction == "r" then
255                            if trace_directions then
256                                setcolor(current,"r",reversed)
257                            end
258                         -- setprop(current,"direction","r")
259                            prop = "r"
260                        elseif direction == "en" then -- european number
261                            if trace_directions then
262                                setcolor(current,"l")
263                            end
264                         -- setprop(current,"direction","l")
265                            prop = "l"
266                        elseif direction == "al" then -- arabic letter
267                            if trace_directions then
268                                setcolor(current,"r")
269                            end
270                         -- setprop(current,"direction","r")
271                            prop = "r"
272                        elseif direction == "an" then -- arabic number
273                            -- needs a better scanner as it can be a float
274                            if trace_directions then
275                                setcolor(current,"l") -- was r
276                            end
277                         -- setprop(current,"direction","n") -- was r
278                            prop = "n"
279                        elseif direction == "lro" then -- Left-to-Right Override -> right becomes left
280                            top        = top + 1
281                            stack[top] = { override, embedded }
282                            override   = -1
283                            obsolete[#obsolete+1] = current
284                            goto obsolete
285                        elseif direction == "rlo" then -- Right-to-Left Override -> left becomes right
286                            top        = top + 1
287                            stack[top] = { override, embedded }
288                            override   = 1
289                            obsolete[#obsolete+1] = current
290                            goto obsolete
291                        elseif direction == "lre" then -- Left-to-Right Embedding -> lefttoright_code
292                            top        = top + 1
293                            stack[top] = { override, embedded }
294                            embedded   = 1
295                            obsolete[#obsolete+1] = current
296                            goto obsolete
297                        elseif direction == "rle" then -- Right-to-Left Embedding -> righttoleft_code
298                            top        = top + 1
299                            stack[top] = { override, embedded }
300                            embedded   = -1
301                            obsolete[#obsolete+1] = current
302                            goto obsolete
303                        elseif direction == "pdf" then -- Pop Directional Format
304                            if top > 0 then
305                                local s  = stack[top]
306                                override = s[1]
307                                embedded = s[2]
308                                top      = top - 1
309                            else
310                                override = 0
311                                embedded = 0
312                            end
313                            obsolete[#obsolete+1] = current
314                            goto obsolete
315                        elseif trace_directions then
316                            setcolor(current)
317                         -- setprop(current,"direction",true)
318                        else
319                         -- setprop(current,"direction",true)
320                        end
321                    end
322                else
323                 -- setprop(current,"direction",true)
324                end
325            elseif id == glue_code then
326                if getsubtype(current) == parfillskip_code then
327                 -- setprop(current,"direction","!")
328                    prop = "!"
329                else
330                 -- setprop(current,"direction","g")
331                    prop = "g"
332                end
333            elseif id == kern_code then
334             -- setprop(current,"direction","k")
335                prop = "k"
336            elseif id == dir_code then
337                local direction, pop = getdirection(current)
338                if direction == righttoleft_code then
339                    if not pop then
340                        autodir = -1
341                    elseif embedded and embedded~= 0 then
342                        autodir = embedded
343                    else
344                        autodir = 0
345                    end
346                elseif direction == lefttoright_code then
347                    if not pop then
348                        autodir = 1
349                    elseif embedded and embedded~= 0 then
350                        autodir = embedded
351                    else
352                        autodir = 0
353                    end
354                end
355                textdir = autodir
356             -- setprop(current,"direction",true)
357            elseif id == par_code and startofpar(current) then
358                local direction = getdirection(current)
359                if direction == righttoleft_code then
360                    autodir = -1
361                elseif direction == lefttoright_code then
362                    autodir = 1
363                end
364                pardir  = autodir
365                textdir = pardir
366             -- setprop(current,"direction",true)
367            else
368             -- setprop(current,"direction",true)
369            end
370            setprop(current,"direction",prop)
371          ::obsolete::
372            current = next
373        end
374    end
375
376    -- todo: track if really needed
377    -- todo: maybe we need to set the property (as it can be a copied list)
378
379    if done and strip then
380        local n = #obsolete
381        if n > 0 then
382            for i=1,n do
383                remove_node(head,obsolete[i],true)
384            end
385            if trace_directions then
386                report_directions("%s character nodes removed",n)
387            end
388        end
389    end
390
391    local state    = false
392    local last     = false
393    local collapse = true
394    current        = head
395
396    -- todo: textdir
397    -- todo: inject before parfillskip
398
399    while current do
400        local id = getid(current)
401        if id == math_code then
402            -- todo: this might be tricky nesting
403            current = getnext(endofmath(getnext(current)))
404        else
405            local cp = getprop(current,"direction")
406            if cp == "n" then
407                local swap = state == "r"
408                if swap then
409                    head = insertnodebefore(head,current,startdir(lefttoright_code))
410                end
411                setprop(current,"direction",true)
412                while true do
413                    local n = getnext(current)
414                    if n and getprop(n,"direction") == "n" then
415                        current = n
416                        setprop(current,"direction",true)
417                    else
418                        break
419                    end
420                end
421                if swap then
422                    head, current = insertnodeafter(head,current,stopdir(lefttoright_code))
423                end
424            elseif cp == "l" then
425                if state ~= "l" then
426                    if state == "r" then
427                        head = insertnodebefore(head,last or current,stopdir(righttoleft_code))
428                    end
429                    head  = insertnodebefore(head,current,startdir(lefttoright_code))
430                    state = "l"
431                    done  = true
432                end
433                last  = false
434            elseif cp == "r" then
435                if state ~= "r" then
436                    if state == "l" then
437                        head = insertnodebefore(head,last or current,stopdir(lefttoright_code))
438                    end
439                    head  = insertnodebefore(head,current,startdir(righttoleft_code))
440                    state = "r"
441                    done  = true
442                end
443                last = false
444            elseif collapse then
445                if cp == "k" or cp == "g" then
446                    last = last or current
447                else
448                    last = false
449                end
450            else
451                if state == "r" then
452                    head = insertnodebefore(head,current,stopdir(righttoleft_code))
453                elseif state == "l" then
454                    head = insertnodebefore(head,current,stopdir(lefttoright_code))
455                end
456                state = false
457                last  = false
458            end
459            setprop(current,"direction",true)
460        end
461        local next = getnext(current)
462        if next then
463            current = next
464        else
465            local sd = (state == "r" and stopdir(righttoleft_code)) or (state == "l" and stopdir(lefttoright_code))
466            if sd then
467                if id == glue_code and getsubtype(current) == parfillskip_code then
468                    head = insertnodebefore(head,current,sd)
469                else
470                    head = insertnodeafter(head,current,sd)
471                end
472            end
473            break
474        end
475    end
476
477    return head
478
479end
480
481directions.installhandler(interfaces.variables.default,process)
482