summaryrefslogtreecommitdiff
path: root/tex/context/base/math-tag.lua
blob: ab5902dd41633a8cdb7348d3985657caf618ccbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
if not modules then modules = { } end modules ['math-tag'] = {
    version   = 1.001,
    comment   = "companion to math-ini.mkiv",
    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
    copyright = "PRAGMA ADE / ConTeXt Development Team",
    license   = "see context related readme files"
}

-- use lpeg matchers

local find, match = string.find, string.match
local insert, remove = table.insert, table.remove

local attributes, nodes = attributes, nodes

local set_attributes      = nodes.setattributes
local traverse_nodes      = node.traverse

local nodecodes           = nodes.nodecodes

local math_noad_code      = nodecodes.noad           -- attr nucleus sub sup
local math_accent_code    = nodecodes.accent         -- attr nucleus sub sup accent
local math_radical_code   = nodecodes.radical        -- attr nucleus sub sup left degree
local math_fraction_code  = nodecodes.fraction       -- attr nucleus sub sup left right
local math_box_code       = nodecodes.subbox         -- attr list
local math_sub_code       = nodecodes.submlist       -- attr list
local math_char_code      = nodecodes.mathchar       -- attr fam char
local math_textchar_code  = nodecodes.mathtextchar   -- attr fam char
local math_delim_code     = nodecodes.delim          -- attr small_fam small_char large_fam large_char
local math_style_code     = nodecodes.style          -- attr style
local math_choice_code    = nodecodes.choice         -- attr display text script scriptscript
local math_fence_code     = nodecodes.fence          -- attr subtype

local hlist_code          = nodecodes.hlist
local vlist_code          = nodecodes.vlist
local glyph_code          = nodecodes.glyph
local glue_code           = nodecodes.glue

local a_tagged            = attributes.private('tagged')
local a_exportstatus      = attributes.private('exportstatus')
local a_mathcategory      = attributes.private('mathcategory')
local a_mathmode          = attributes.private('mathmode')

local tags                = structures.tags

local start_tagged        = tags.start
local restart_tagged      = tags.restart
local stop_tagged         = tags.stop
local taglist             = tags.taglist

local chardata            = characters.data

local getmathcode         = tex.getmathcode
local mathcodes           = mathematics.codes
local ordinary_code       = mathcodes.ordinary
local variable_code       = mathcodes.variable

local process

local function processsubsup(start)
    -- At some point we might need to add an attribute signaling the
    -- super- and subscripts because TeX and MathML use a different
    -- order.
    local nucleus, sup, sub = start.nucleus, start.sup, start.sub
    if sub then
        if sup then
            start[a_tagged] = start_tagged("msubsup")
            process(nucleus)
            process(sub)
            process(sup)
            stop_tagged()
        else
            start[a_tagged] = start_tagged("msub")
            process(nucleus)
            process(sub)
            stop_tagged()
        end
    elseif sup then
        start[a_tagged] = start_tagged("msup")
        process(nucleus)
        process(sup)
        stop_tagged()
    else
        process(nucleus)
    end
end

-- todo: check function here and keep attribute the same

-- todo: variants -> original

local actionstack = { }

process = function(start) -- we cannot use the processor as we have no finalizers (yet)
    while start do
        local id = start.id
        if id == math_char_code then
            local char = start.char
            -- check for code
            local a = start[a_mathcategory]
            if a then
                a = { detail = a }
            end
            local code = getmathcode(char)
            if code then
                code = code[1]
            end
            local tag
            if code == ordinary_code or code == variable_code then
                local ch = chardata[char]
                local mc = ch and ch.mathclass
                if mc == "number" then
                    tag = "mn"
                elseif mc == "variable" or not mc then -- variable is default
                    tag = "mi"
                else
                    tag = "mo"
                end
            else
                tag = "mo"
            end
            start[a_tagged] = start_tagged(tag,a)
            stop_tagged()
            break -- okay?
        elseif id == math_textchar_code then
            -- check for code
            local a = start[a_mathcategory]
            if a then
                start[a_tagged] = start_tagged("ms",{ detail = a })
            else
                start[a_tagged] = start_tagged("ms")
            end
            stop_tagged()
            break
        elseif id == math_delim_code then
            -- check for code
            start[a_tagged] = start_tagged("mo")
            stop_tagged()
            break
        elseif id == math_style_code then
            -- has a next
        elseif id == math_noad_code then
            processsubsup(start)
        elseif id == math_box_code or id == hlist_code or id == vlist_code then
            -- keep an eye on math_box_code and see what ends up in there
            local attr = start[a_tagged]
            local last = attr and taglist[attr]
            if last and find(last[#last],"formulacaption[:%-]") then
                -- leave alone, will nicely move to the outer level
            else
                local text = start_tagged("mtext")
                start[a_tagged] = text
                local list = start.list
                if not list then
                    -- empty list
                elseif not attr then
                    -- box comes from strange place
                    set_attributes(list,a_tagged,text)
                else
                    -- Beware, the first node in list is the actual list so we definitely
                    -- need to nest. This approach is a hack, maybe I'll make a proper
                    -- nesting feature to deal with this at another level. Here we just
                    -- fake structure by enforcing the inner one.
                    local tagdata = taglist[attr]
                    local common = #tagdata + 1
                    local function runner(list) -- quite inefficient
                        local cache = { } -- we can have nested unboxed mess so best local to runner
                        for n in traverse_nodes(list) do
                            local id = n.id
                            local aa = n[a_tagged]
                            if aa then
                                local ac = cache[aa]
                                if not ac then
                                    local tagdata = taglist[aa]
                                    local extra = #tagdata
                                    if common <= extra then
                                        for i=common,extra do
                                            ac = restart_tagged(tagdata[i]) -- can be made faster
                                        end
                                        for i=common,extra do
                                            stop_tagged() -- can be made faster
                                        end
                                    else
                                        ac = text
                                    end
                                    cache[aa] = ac
                                end
                                n[a_tagged] = ac
                            else
                                n[a_tagged] = text
                            end
                            if id == hlist_code or id == vlist_code then
                                runner(n.list)
                            end
                        end
                    end
                    runner(list)
                end
                stop_tagged()
            end
        elseif id == math_sub_code then
            local list = start.list
            if list then
                local attr = start[a_tagged]
                local last = attr and taglist[attr]
                local action = last and match(last[#last],"maction:(.-)%-")
                if action and action ~= "" then
                    if actionstack[#actionstack] == action then
                        start[a_tagged] = start_tagged("mrow")
                        process(list)
                        stop_tagged()
                    else
                        insert(actionstack,action)
                        start[a_tagged] = start_tagged("mrow",{ detail = action })
                        process(list)
                        stop_tagged()
                        remove(actionstack)
                    end
                else
                    start[a_tagged] = start_tagged("mrow")
                    process(list)
                    stop_tagged()
                end
            end
        elseif id == math_fraction_code then
            local num, denom, left, right = start.num, start.denom, start.left, start.right
            if left then
               left[a_tagged] = start_tagged("mo")
               process(left)
               stop_tagged()
            end
            start[a_tagged] = start_tagged("mfrac")
            process(num)
            process(denom)
            stop_tagged()
            if right then
                right[a_tagged] = start_tagged("mo")
                process(right)
                stop_tagged()
            end
        elseif id == math_choice_code then
            local display, text, script, scriptscript = start.display, start.text, start.script, start.scriptscript
            if display then
                process(display)
            end
            if text then
                process(text)
            end
            if script then
                process(script)
            end
            if scriptscript then
                process(scriptscript)
            end
        elseif id == math_fence_code then
            local delim = start.delim
            local subtype = start.subtype
            if subtype == 1 then
                -- left
                start[a_tagged] = start_tagged("mfenced")
                if delim then
                    start[a_tagged] = start_tagged("mleft")
                    process(delim)
                    stop_tagged()
                end
            elseif subtype == 2 then
                -- middle
                if delim then
                    start[a_tagged] = start_tagged("mmiddle")
                    process(delim)
                    stop_tagged()
                end
            elseif subtype == 3 then
                if delim then
                    start[a_tagged] = start_tagged("mright")
                    process(delim)
                    stop_tagged()
                end
                stop_tagged()
            else
                -- can't happen
            end
        elseif id == math_radical_code then
            local left, degree = start.left, start.degree
            if left then
                start_tagged("")
                process(left) -- root symbol, ignored
                stop_tagged()
            end
            if degree then -- not good enough, can be empty mlist
                start[a_tagged] = start_tagged("mroot")
                processsubsup(start)
                process(degree)
                stop_tagged()
            else
                start[a_tagged] = start_tagged("msqrt")
                processsubsup(start)
                stop_tagged()
            end
        elseif id == math_accent_code then
            local accent, bot_accent = start.accent, start.bot_accent
            if bot_accent then
                if accent then
                    start[a_tagged] = start_tagged("munderover",{ detail = "accent" })
                    processsubsup(start)
                    process(bot_accent)
                    process(accent)
                    stop_tagged()
                else
                    start[a_tagged] = start_tagged("munder",{ detail = "accent" })
                    processsubsup(start)
                    process(bot_accent)
                    stop_tagged()
                end
            elseif accent then
                start[a_tagged] = start_tagged("mover",{ detail = "accent" })
                processsubsup(start)
                process(accent)
                stop_tagged()
            else
                processsubsup(start)
            end
        elseif id == glue_code then
            start[a_tagged] = start_tagged("mspace")
            stop_tagged()
        else
            start[a_tagged] = start_tagged("merror", { detail = nodecodes[i] })
            stop_tagged()
        end
        start = start.next
    end
end

function noads.handlers.tags(head,style,penalties)
    local v_math = start_tagged("math")
    local v_mrow = start_tagged("mrow")
    local v_mode = head[a_mathmode]
    head[a_tagged] = v_math
    head[a_tagged] = v_mrow
    tags.setattributehash(v_math,"mode",v_mode == 1 and "display" or "inline")
    process(head)
    stop_tagged()
    stop_tagged()
    return true
end