typo-dir.lmt /size: 7346 b    last modification: 2021-10-28 13:51
1if not modules then modules = { } end modules ['typo-dir'] = {
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-- When we started with this, there were some issues in luatex so we needed to take care of
10-- intereferences. Some has been improved but we stil might end up with each node having a
11-- dir property. Now, the biggest problem is that there is an official bidi algorithm but
12-- some searching on the web shows that there are many confusing aspects and therefore
13-- proposals circulate about (sometimes imcompatible ?) improvements. In the end it all boils
14-- down to the lack of willingness to tag an input source. Of course tagging of each number
15-- and fenced strip is somewhat over the top, but now it has to be captured in logic. Texies
16-- normally have no problem with tagging but we need to handle any input. So, what we have
17-- done here (over the years) is starting from what we expect to see happen, especially with
18-- respect to punctation, numbers and fences. Eventually alternative algorithms will be provides
19-- so that users can choose (the reason why suggestion sfor improvements circulate on the web
20-- is that it is non trivial to predict the expected behaviour so one hopes that the ditor
21-- and the rest of the machinery match somehow. Anyway, the fun of tex is that it has no hard
22-- coded behavior. And ... we also want to have more debugging and extras and ... so we want
23-- a flexible approach. In the end we will have:
24--
25-- = full tagging (mechanism turned off)
26-- = half tagging (the current implementation)
27-- = unicode version x interpretation (several depending on the evolution)
28
29local next, type = next, type
30local format, insert, sub, find, match = string.format, table.insert, string.sub, string.find, string.match
31
32local nodes, node = nodes, node
33
34local trace_textdirections  = false  trackers.register("typesetters.directions.text", function(v) trace_textdirections = v end)
35local trace_mathdirections  = false  trackers.register("typesetters.directions.math", function(v) trace_mathdirections = v end)
36local trace_directions      = false  trackers.register("typesetters.directions",      function(v) trace_textdirections = v trace_mathdirections = v end)
37
38local one_too               = false  directives.register("typesetters.directions.onetoo", function(v) one_too = v end)
39
40local report_textdirections = logs.reporter("typesetting","text directions")
41----- report_mathdirections = logs.reporter("typesetting","math directions")
42
43local texsetattribute       = tex.setattribute
44local unsetvalue            = attributes.unsetvalue
45
46local nuts                  = nodes.nuts
47local getnext               = nuts.getnext
48local getattr               = nuts.getattr
49
50local enableaction          = nodes.tasks.enableaction
51local tracers               = nodes.tracers
52local setcolor              = tracers.colors.set
53local resetcolor            = tracers.colors.reset
54
55local implement             = interfaces.implement
56
57local directions            = typesetters.directions or { }
58typesetters.directions      = directions
59
60local a_directions          = attributes.private('directions')
61
62local variables             = interfaces.variables
63local v_global              = variables["global"]
64local v_local               = variables["local"]
65local v_on                  = variables.on
66local v_yes                 = variables.yes
67
68local m_enabled             = 0x00000040 -- 2^6 64
69local m_global              = 0x00000080 -- 2^7
70local m_fences              = 0x00000100 -- 2^8
71
72local handlers              = { }
73local methods               = { }
74local lastmethod            = 0
75
76local function installhandler(name,handler)
77    local method = methods[name]
78    if not method then
79        lastmethod    = lastmethod + 1
80        method        = lastmethod
81        methods[name] = method
82    end
83    handlers[method] = handler
84    return method
85end
86
87directions.handlers       = handlers
88directions.installhandler = installhandler
89
90local function tomode(specification)
91    local scope = specification.scope
92    local mode
93    if scope == v_global or scope == v_on then
94        mode = m_enabled + m_global
95    elseif scope == v_local then
96        mode = m_enabled
97    else
98        return 0
99    end
100    local method = methods[specification.method]
101    if method then
102        mode = mode + method
103    else
104        return 0
105    end
106    if specification.fences == v_yes then
107        mode = mode + m_fences
108    end
109    return mode
110end
111
112local function getglobal(a)
113    return a and a > 0 and (a & m_global) ~= 0
114end
115
116local function getfences(a)
117    return a and a > 0 and (a & m_fences) ~= 0
118end
119
120local function getmethod(a)
121    return a and a > 0 and a % m_enabled or 0
122end
123
124directions.tomode         = tomode
125directions.getglobal      = getglobal
126directions.getfences      = getfences
127directions.getmethod      = getmethod
128directions.installhandler = installhandler
129
130-- beware: in dha we have character properties and in dua|b we have direction properties
131
132function directions.setcolor(current,direction,reversed,mirror)
133    if mirror then
134        setcolor(current,"bidi:mirrored")
135    elseif direction == "l" then
136        setcolor(current,reversed and "bidi:left:reversed" or "bidi:left:original")
137    elseif direction == "r" then
138        setcolor(current,reversed and "bidi:right:reversed" or "bidi:right:original")
139    else
140        resetcolor(current)
141    end
142end
143
144implement {
145    name      = "getbidimode",
146    actions   = { tomode, context },
147    arguments = {
148        {
149            { "scope" },
150            { "method" },
151            { "fences" },
152        }
153    }
154}
155
156local enabled = false
157
158local starttiming = statistics.starttiming
159local stoptiming  = statistics.stoptiming
160
161-- If we have hbox{!} then the hbox determines the direction but we can consider
162-- a fast analysis, not that it matters much because there's nothing to swap in
163-- the list unless one glyphs becomes multiple (can that really happen?).
164--
165-- \enabledirectives[typesetters.directions.onetoo]
166
167function directions.handler(head,where,direction)
168    local only_one = not getnext(head)
169    if only_one and not one_too then
170        return head
171    end
172    local attr = getattr(head,a_directions)
173    if not attr or attr == 0 then
174        return head
175    end
176    local method  = getmethod(attr)
177    local handler = handlers[method]
178    if not handler then
179        return head
180    end
181    starttiming(directions)
182    head = handler(head,direction,only_one,where)
183    stoptiming(directions)
184    return head
185end
186
187statistics.register("text directions", function()
188    if enabled then
189        return statistics.elapsedseconds(directions)
190    end
191end)
192
193function directions.set(n) -- todo: names and numbers
194    if not enabled then
195        if trace_textdirections then
196            report_textdirections("enabling directions handler")
197        end
198        enableaction("processors","typesetters.directions.handler")
199        enabled = true
200    end
201    if not n or n == 0 then
202        n = unsetvalue
203        -- maybe tracing
204    end
205    texsetattribute(a_directions,n)
206end
207
208implement {
209    name      = "setdirection",
210    arguments = "integer",
211    actions   = directions.set
212}
213