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