typo-dir.lua /size: 7396 b    last modification: 2020-07-01 14:35
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 band                  = bit32.band
44
45local texsetattribute       = tex.setattribute
46local unsetvalue            = attributes.unsetvalue
47
48local nuts                  = nodes.nuts
49local getnext               = nuts.getnext
50local getattr               = nuts.getattr
51
52local enableaction          = nodes.tasks.enableaction
53local tracers               = nodes.tracers
54local setcolor              = tracers.colors.set
55local resetcolor            = tracers.colors.reset
56
57local implement             = interfaces.implement
58
59local directions            = typesetters.directions or { }
60typesetters.directions      = directions
61
62local a_directions          = attributes.private('directions')
63
64local variables             = interfaces.variables
65local v_global              = variables["global"]
66local v_local               = variables["local"]
67local v_on                  = variables.on
68local v_yes                 = variables.yes
69
70local m_enabled             = 0x00000040 -- 2^6 64
71local m_global              = 0x00000080 -- 2^7
72local m_fences              = 0x00000100 -- 2^8
73
74local handlers              = { }
75local methods               = { }
76local lastmethod            = 0
77
78local function installhandler(name,handler)
79    local method = methods[name]
80    if not method then
81        lastmethod    = lastmethod + 1
82        method        = lastmethod
83        methods[name] = method
84    end
85    handlers[method] = handler
86    return method
87end
88
89directions.handlers       = handlers
90directions.installhandler = installhandler
91
92local function tomode(specification)
93    local scope = specification.scope
94    local mode
95    if scope == v_global or scope == v_on then
96        mode = m_enabled + m_global
97    elseif scope == v_local then
98        mode = m_enabled
99    else
100        return 0
101    end
102    local method = methods[specification.method]
103    if method then
104        mode = mode + method
105    else
106        return 0
107    end
108    if specification.fences == v_yes then
109        mode = mode + m_fences
110    end
111    return mode
112end
113
114local function getglobal(a)
115    return a and a > 0 and band(a,m_global) ~= 0
116end
117
118local function getfences(a)
119    return a and a > 0 and band(a,m_fences) ~= 0
120end
121
122local function getmethod(a)
123    return a and a > 0 and a % m_enabled or 0
124end
125
126directions.tomode         = tomode
127directions.getglobal      = getglobal
128directions.getfences      = getfences
129directions.getmethod      = getmethod
130directions.installhandler = installhandler
131
132-- beware: in dha we have character properties and in dua|b we have direction properties
133
134function directions.setcolor(current,direction,reversed,mirror)
135    if mirror then
136        setcolor(current,"bidi:mirrored")
137    elseif direction == "l" then
138        setcolor(current,reversed and "bidi:left:reversed" or "bidi:left:original")
139    elseif direction == "r" then
140        setcolor(current,reversed and "bidi:right:reversed" or "bidi:right:original")
141    else
142        resetcolor(current)
143    end
144end
145
146implement {
147    name      = "getbidimode",
148    actions   = { tomode, context },
149    arguments = {
150        {
151            { "scope" },
152            { "method" },
153            { "fences" },
154        }
155    }
156}
157
158local enabled = false
159
160local starttiming = statistics.starttiming
161local stoptiming  = statistics.stoptiming
162
163-- If we have hbox{!} then the hbox determines the direction but we can consider
164-- a fast analysis, not that it matters much because there's nothing to swap in
165-- the list unless one glyphs becomes multiple (can that really happen?).
166--
167-- \enabledirectives[typesetters.directions.onetoo]
168
169function directions.handler(head,where,_,_,direction)
170    local only_one = not getnext(head)
171    if only_one and not one_too then
172        return head
173    end
174    local attr = getattr(head,a_directions)
175    if not attr or attr == 0 then
176        return head
177    end
178    local method  = getmethod(attr)
179    local handler = handlers[method]
180    if not handler then
181        return head
182    end
183    starttiming(directions)
184    head = handler(head,direction,only_one,where)
185    stoptiming(directions)
186    return head
187end
188
189statistics.register("text directions", function()
190    if enabled then
191        return statistics.elapsedseconds(directions)
192    end
193end)
194
195function directions.set(n) -- todo: names and numbers
196    if not enabled then
197        if trace_textdirections then
198            report_textdirections("enabling directions handler")
199        end
200        enableaction("processors","typesetters.directions.handler")
201        enabled = true
202    end
203    if not n or n == 0 then
204        n = unsetvalue
205        -- maybe tracing
206    end
207    texsetattribute(a_directions,n)
208end
209
210implement {
211    name      = "setdirection",
212    arguments = "integer",
213    actions   = directions.set
214}
215