back-exp-imp-mth.lmt /size: 30 Kb    last modification: 2024-01-16 09:02
1if not modules then modules = { } end modules ['back-exp-imp-mth'] = {
2    version   = 1.001,
3    comment   = "companion to back-exp.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
9local sub = string.sub
10local utfchar, utfvalues = utf.char, utf.values
11local setmetatableindex, concat = table.setmetatableindex, table.concat
12
13local structurestags = structures.tags
14local specifications = structurestags.specifications
15local locatedtag     = structurestags.locatedtag
16
17local backend        = structurestags.backend
18
19local setattribute   = backend.setattribute
20local extras         = backend.extras
21local checks         = backend.checks
22local finalizers     = backend.finalizers
23
24----- bpfactor       = number.dimenfactors.bp
25----- f_points       = string.formatters["%p"]
26local f_em           = string.formatters["%.6Nem"]
27
28local implement      = interfaces.implement
29
30do
31
32    local automathrows   = true  directives.register("export.math.autorows",   function(v) automathrows   = v end)
33    local automathapply  = true  directives.register("export.math.autoapply",  function(v) automathapply  = v end)
34    local automathnumber = true  directives.register("export.math.autonumber", function(v) automathnumber = v end)
35    local automathstrip  = true  directives.register("export.math.autostrip",  function(v) automathstrip  = v end)
36
37    local functions      = mathematics.categories.functions
38
39    local function collapse(di,i,data,ndata,detail,element)
40        local collapsing = di.data
41        if data then
42            di.element = element
43            di.detail = nil
44            i = i + 1
45            while i <= ndata do
46                local dn = data[i]
47                if dn.detail == detail then
48                    collapsing[#collapsing+1] = dn.data[1]
49                    dn.skip = "ignore"
50                    i = i + 1
51                else
52                    break
53                end
54            end
55        end
56        return i
57    end
58
59    local function collapse_mn(di,i,data,ndata)
60        -- this is tricky ... we need to make sure that we wrap in mrows if we want
61        -- to bypass this one
62        local collapsing = di.data
63        if data then
64            i = i + 1
65            while i <= ndata do
66                local dn = data[i]
67                local tg = dn.tg
68                if tg == "mn" then
69                    collapsing[#collapsing+1] = dn.data[1]
70                    dn.skip = "ignore"
71                    i = i + 1
72                elseif tg == "mo" then
73                    local d = dn.data[1]
74                    if d == "." then
75                        collapsing[#collapsing+1] = d
76                        dn.skip = "ignore"
77                        i = i + 1
78                    else
79                        break
80                    end
81                else
82                    break
83                end
84            end
85        end
86        return i
87    end
88
89    -- maybe delay __i__ till we need it
90
91    local apply_function = {
92        {
93            element = "mo",
94         -- comment = "apply function",
95         -- data    = { utfchar(0x2061) },
96            data    = { "&#x2061;" },
97            nature  = "mixed",
98        }
99    }
100
101    local functioncontent = { }
102
103    setmetatableindex(functioncontent,function(t,k)
104        local v = { { content = k } }
105        t[k] = v
106        return v
107    end)
108
109    local dummy_nucleus = {
110        element   = "mtext",
111        data      = { content = "" },
112        nature    = "inline",
113        comment   = "dummy nucleus",
114        fulltag   = "mtext>0"
115    }
116
117    local function accentchar(d) -- see mkiv for old one
118        local detail = tonumber(d.detail)
119        if detail then
120            local d1 = d.data[1]
121            if d1 and d1.tg == "mrow" then
122                --
123                d.element = "mrow" -- used
124                d.detail = nil
125                local s = specifications[d.fulltag] -- hm, used when not set
126                s.detail = nil
127                --
128                d1.element = "mo" -- used
129             -- d1.detail = nil
130                d1.nature = "mixed"
131                d1.data = { { content = utfchar(detail) } }
132                return d
133            end
134        end
135    end
136
137    local no_mrow = {
138        mrow     = true,
139        mfenced  = true,
140        mfrac    = true,
141        mroot    = true,
142        msqrt    = true,
143        mtable   = true,
144        mi       = true,
145        mo       = true,
146        mn       = true,
147        mspace   = true,
148
149     -- mstacker    = true,
150     -- mextensible = true,
151    }
152
153    local function checkmath(root) -- we can provide utf.toentities as an option
154        local data = root.data
155        if data then
156            local ndata = #data
157            local roottg = root.tg
158            if roottg == "mo" then
159                local s = specifications[root.fulltag]
160                local c = s.class
161                if c == "open" or c == "close" or c == "middle" then
162                    root.attributes = {
163                        maxsize = 1
164                    }
165                end
166            elseif roottg == "msubsup" then
167                -- kind of tricky: we have a different order in display mode
168                local nucleus, superscript, subscript
169                if ndata > 3 then
170                    -- error
171                else
172                    for i=1,ndata do
173                        local di = data[i]
174                        if not di then
175                            -- weird
176                        elseif di.content then
177                            -- text
178                        else
179                            local s = specifications[di.fulltag]
180                            if s.subscript then
181                                subscript = i
182                            elseif s.superscript then
183                                superscript = i
184                            else
185                                nucleus = i
186                            end
187                        end
188                    end
189                    if superscript or subscript then
190                        -- we probably always have 3 anyway ... needs checking
191                        local nuc = nucleus     and data[nucleus]
192                        local sub = subscript   and data[subscript]
193                        local sup = superscript and data[superscript]
194                        local n = 0 -- play safe
195                        if nuc then n = n + 1 ; data[n] = nuc end
196                        if sub then n = n + 1 ; data[n] = sub end
197                        if sup then n = n + 1 ; data[n] = sup end
198                    end
199                end
200         -- elseif roottg == "msup" or roottg == "msub" then
201         --     -- m$^2$
202         --     if ndata == 1 then
203         --         local d = data[1]
204         --         data[2] = d
205         --         d.__i__ = 2
206         --         data[1] = dummy_nucleus
207         --     end
208            elseif roottg == "mfenced" then
209                local s = specifications[root.fulltag]
210                local o = s.operator
211                if o then
212                    root.skip = "comment"
213                 -- root.content = utfchar(o) -- use embedded for now
214                else
215                    local l = s.left
216                    local m = s.middle
217                    local r = s.right
218                    if l then
219                        l = utfchar(l)
220                    end
221                    if m then
222                        local t = { }
223                        for i=1,#m do
224                            t[i] = utfchar(m[i])
225                        end
226                        m = concat(t)
227                    end
228                    if r then
229                        r = utfchar(r)
230                    end
231                    root.attributes = {
232                        open       = l,
233                        separators = m,
234                        close      = r,
235                    }
236                end
237            elseif roottg == "mstacker" then
238            end
239            if ndata == 0 then
240                root.skip = "comment" -- get rid of weird artefacts
241                root.nota = "weird"
242                return
243            elseif ndata == 1 then
244                local d = data[1]
245                if not d or d == "" then
246                    root.skip = "comment"
247                    return
248                elseif d.content then
249                    return
250                else -- if ndata == 1 then
251                    local tg = d.tg
252                    if automathrows and (roottg == "mrow" or roottg == "mtext") then
253                        -- maybe just always ! check spec first
254                        -- or we can have checks.* for each as we then can flatten
255                        if no_mrow[tg] then
256                            root.skip = "comment"
257                        end
258                    elseif roottg == "mo" then
259                        if tg == "mo" then
260                            root.skip = "comment"
261                        end
262                    end
263                end
264            end
265            local i = 1
266            while i <= ndata do                   -- -- -- TOO MUCH NESTED CHECKING -- -- --
267                local di = data[i]
268                if di and not di.content then
269                    local tg = di.tg
270                    if tg == "math" then
271                     -- di.element = "mrow" -- when properties
272                        di.skip = "comment"
273                        checkmath(di)
274                        i = i + 1
275                    elseif tg == "mover" then
276                        local s = specifications[di.fulltag]
277                        if s.accent then
278                            local t = s.top
279                            local d = di.data
280                            -- todo: accent = "false" (for scripts like limits)
281                            di.attributes = {
282                                accent = "true",
283                            }
284                            -- todo: p.topfixed
285                            if t then
286                                -- mover
287                                if true then -- we don't go here any more
288                                    local dd = d[1].data
289                                    if dd then
290                                        dd[1].content = utfchar(t)
291                                    end
292                                end
293                                --
294                                di.data = { d[2], d[1] }
295                            end
296                        else
297                            -- can't happen
298                        end
299                        checkmath(di)
300                        i = i + 1
301                    elseif tg == "munder" then
302                        local s = specifications[di.fulltag]
303                        if s.accent then
304                            local b = s.bottom
305                            local d = di.data
306                            -- todo: accent = "false" (for scripts like limits)
307                            di.attributes = {
308                                accent = "true",
309                            }
310                         -- todo: p.bottomfixed
311                            if b then
312                                -- munder
313                                if true then -- we don't go here any more
314                                    local dd = d[2].data
315                                    if dd then
316                                        dd[1].content = utfchar(b)
317                                    end
318                                end
319                            end
320                        else
321                            -- can't happen
322                        end
323                        checkmath(di)
324                        i = i + 1
325                    elseif tg == "munderover" then
326                        local s = specifications[di.fulltag]
327                        if s.accent then
328                            local t = s.top
329                            local b = s.bottom
330                            local d = di.data
331                            -- todo: accent      = "false" (for scripts like limits)
332                            -- todo: accentunder = "false" (for scripts like limits)
333                            di.attributes = {
334                                accent      = "true",
335                                accentunder = "true",
336                            }
337                         -- todo: p.topfixed
338                         -- todo: p.bottomfixed
339                            if t and b then
340                                -- munderover
341                                if true then -- we don't go here any more
342                                    local dd = d[1].data
343                                    if dd then
344                                        dd[1].content = utfchar(t)
345                                    end
346                                    local dd = d[3].data
347                                    if dd then
348                                        dd[1].content = utfchar(b)
349                                    end
350                                end
351                                di.data = { d[2], d[3], d[1] }
352                            else
353                                -- can't happen
354                            end
355                        else
356                            -- can't happen
357                        end
358                        checkmath(di)
359                        i = i + 1
360                    elseif tg == "mstacker" then
361--                     inspect(di)
362                        local d = di.data
363                        local d1 = d[1]
364                        local d2 = d[2]
365                        local d3 = d[3]
366                        local t1 = d1 and d1.tg
367                        local t2 = d2 and d2.tg
368                        local t3 = d3 and d3.tg
369                        local m  = nil -- d1.data[1]
370                        local t  = nil
371                        local b  = nil
372                        -- only accent when top / bot have stretch
373                        -- normally we flush [base under over] which is better for tagged pdf
374                        if t1 == "mstackermid" then
375                            m = accentchar(d1) -- or m
376                            if t2 == "mstackertop" then
377                                if t3 == "mstackerbot" then
378                                    t = accentchar(d2)
379                                    b = accentchar(d3)
380                                    di.element = "munderover"
381                                    di.data    = { m or d1.data[1], b or d3.data[1], t or d2.data[1] }
382                                else
383                                    t = accentchar(d2)
384                                    di.element = "mover"
385                                    di.data    = { m or d1.data[1], t or d2.data[1] }
386                                end
387                            elseif t2 == "mstackerbot" then
388                                if t3 == "mstackertop" then
389                                    b = accentchar(d2)
390                                    t = accentchar(d3)
391                                    di.element = "munderover"
392                                    di.data    = { m or d1.data[1], t or d3.data[1], m, b or d2.data[1] }
393                                else
394                                    b = accentchar(d2)
395                                    di.element = "munder"
396                                    di.data    = { m or d1.data[1], b or d2.data[1] }
397                                end
398                            else
399-- di.element = "mweird"
400                                -- can't happen
401                            end
402                        else
403                            -- can't happen
404                        end
405                        if t or b then
406                            di.attributes = {
407                                accent      = t and "true" or nil,
408                                accentunder = b and "true" or nil,
409                            }
410                            di.detail = nil
411                        end
412-- inspect(di)
413                        checkmath(di)
414                        i = i + 1
415                    elseif tg == "mroot" then
416                        local data = di.data
417                        local size = #data
418                        if size == 1 then
419                            -- else firefox complains ... code in math-tag (for pdf tagging)
420                            di.element = "msqrt"
421                        elseif size == 2 then
422                            data[1], data[2] = data[2], data[1]
423                        end
424                        checkmath(di)
425                        i = i + 1
426                    elseif tg == "break" then
427                        di.skip = "comment"
428                        i = i + 1
429                    elseif tg == "mspace" then
430                     -- di.empty = true
431                        local s = specifications[di.fulltag]
432                        local e = s and s.emfactor
433                        if e and e ~= 0 then
434                            di.element = "mspace"
435                            di.attributes = {
436                                width = f_em(e),
437                            }
438                        end
439                        i = i + 1
440                    elseif tg == "mtext" then
441                        -- this is only needed for unboxed mtexts ... all kind of special
442                        -- tex border cases and optimizations ... trial and error
443                        local data = di.data
444                        if #data > 1 then
445                            for i=1,#data do
446                                local di = data[i]
447                                local content = di.content
448                                if content then
449                                    data[i] = {
450                                        element = "mtext",
451                                        nature  = "inline",
452                                        data    = { di },
453                                        n       = 0,
454                                    }
455                                elseif di.tg == "math" then
456                                    local di = di.data[1]
457                                    if di then
458                                        data[i] = di
459                                        checkmath(di)
460                                    end
461                                end
462                            end
463                            di.element = "mrow"
464                         -- di.tg = "mrow"
465                         -- di.nature  = "inline"
466                        end
467                        checkmath(di)
468                        i = i + 1
469                    elseif tg == "mrow" and detail then -- hm, falls through
470                        di.detail = nil
471                        checkmath(di)
472                        di = {
473                            element    = "maction",
474                            nature     = "display",
475                            attributes = { actiontype = detail },
476                            data       = { di },
477                            n          = 0,
478                        }
479                        data[i] = di
480                        i = i + 1
481                    else
482                        local category = di.mathcategory
483                        if category then
484                         -- no checkmath(di) here
485                            if category == 1 then -- mo
486                                i = collapse(di,i,data,ndata,detail,"mo")
487                            elseif category == 2 then -- mi
488                                i = collapse(di,i,data,ndata,detail,"mi")
489                            elseif category == 3 then -- mn
490                                i = collapse(di,i,data,ndata,detail,"mn")
491                            elseif category == 4 then -- ms
492                                i = collapse(di,i,data,ndata,detail,"ms")
493                            elseif category >= 1000 then
494                                local apply = category >= 2000
495                                if apply then
496                                    category = category - 1000
497                                end
498                                if tg == "mi" then -- function
499                                    if roottg == "mrow" then
500                                        root.skip = "comment"
501                                        root.element = "function"
502                                    end
503                                    i = collapse(di,i,data,ndata,detail,"mi")
504                                    local tag = functions[category]
505                                    if tag then
506                                        di.data = functioncontent[tag]
507                                    end
508                                    if apply then
509                                        di.after = apply_function
510                                    elseif automathapply then -- make function
511                                        local following
512                                        if i <= ndata then
513                                            -- normally not the case
514                                            following = data[i]
515                                        else
516                                            local parent = di.__p__ -- == root
517                                            if parent.tg == "mrow" then
518                                                parent = parent.__p__
519                                            end
520                                            local index = parent.__i__
521                                            following = parent.data[index+1]
522                                        end
523                                        if following then
524                                            local tg = following.tg
525                                            if tg == "mrow" or tg == "mfenced" then -- we need to figure out the right condition
526                                                di.after = apply_function
527                                            end
528                                        end
529                                    end
530                                else -- some problem
531                                    checkmath(di)
532                                    i = i + 1
533                                end
534                            else
535                                checkmath(di)
536                                i = i + 1
537                            end
538                        elseif automathnumber and tg == "mn" then
539                            checkmath(di)
540                            i = collapse_mn(di,i,data,ndata)
541                        else
542                            checkmath(di)
543                            i = i + 1
544                        end
545                    end
546                else -- can be string or boolean
547                    if parenttg ~= "mtext" and di == " " then
548                        data[i] = false
549                    end
550                    i = i + 1
551                end
552            end
553        end
554    end
555
556    local function stripmath(di)
557        if not di then
558            --
559        elseif di.content then
560            return di
561        else
562            local tg = di.tg
563            if tg == "mtext" or tg == "ms" then
564                return di
565            elseif tg == "mspace" then
566                return di
567            else
568                local data = di.data
569                local ndata = #data
570                local n = 0
571                for i=1,ndata do
572                    local d = data[i]
573                    if d and not d.content then
574                        d = stripmath(d)
575                    end
576                    if d then
577                        local content = d.content
578                        if d.tg == "mspace" then
579                            n = n + 1
580                            data[n] = d
581                            d.data = { }
582                        elseif not content then
583--                      if not content then
584                            n = n + 1
585                            d.__i__ = n
586                            data[n] = d
587--                        elseif content == " " or content == "" then
588--                            if d.tg == "mspace" then
589--                             -- we append or prepend a space to a preceding or following mtext
590--                                local parent = di.__p__
591--                                local index  = di.__i__ -- == i
592--                                local data   = parent.data
593--                                if index > 1 then
594--                                    local d = data[index-1]
595--                                    if d.tg == "mtext" then
596--                                        local dd = d.data
597--                                        local dn = dd[#dd]
598--                                        local dc = dn.content
599--                                        if dc then
600--                                            dn.content = dc .. content
601--                                        end
602--                                    end
603--                                elseif index < ndata then
604--                                    local d = data[index+1]
605--                                    if d.tg == "mtext" then
606--                                        local dd = d.data
607--                                        local dn = dd[1]
608--                                        local dc = dn.content
609--                                        if dc then
610--                                            dn.content = content .. dc
611--                                        end
612--                                    end
613--                                end
614--                            end
615                        else
616                            n = n + 1
617                            data[n] = d
618                        end
619                    end
620                end
621                for i=ndata,n+1,-1 do
622                    data[i] = nil
623                end
624                if #data > 0 then
625                    return di
626                end
627            end
628        end
629    end
630
631    function checks.math(di)
632        if di.skip == "comment" then
633            -- already done, kind of weird, happens in mathmatrix, maybe some collapse
634            -- issue that i need to look into
635        else
636            local specification = specifications[di.fulltag]
637            local mode = specification and specification.mode == "display" and "block" or "inline"
638            di.attributes = {
639                ["display"]    = mode,
640                ["xmlns:m"]    = mathmlns,
641                ["xmlns:math"] = mathmlns,
642            }
643            -- can be option if needed:
644            if mode == "inline" then
645             -- di.nature = "mixed"  -- else spacing problem (maybe inline)
646                di.nature = "inline" -- we need to catch x$X$x and x $X$ x
647            else
648                di.nature = "display"
649            end
650            if automathstrip then
651                stripmath(di)
652            end
653            checkmath(di)
654        end
655    end
656
657    -- this one can replace some of the previous code .. todo (test on mathmatrix)
658
659    -- ignore with no data can be removed
660
661    local function checked(d)
662        local n = #d
663        if n == 1 then
664            local di = d[1]
665            local tg = di.tg
666            if tg == "ignore" then
667                -- todo: we can move ignore's data one level up
668                return 1
669            elseif di.content then
670                return 1
671            else
672                local dd = di.data
673                if #dd > 0 and checked(dd) > 0 then
674                    return 1
675                else
676                    return 0
677                end
678            end
679        else
680            local m = 0
681            for i=1,n do
682                local di = d[i]
683                local tg = di.tg
684                if tg == "ignore" then
685                    -- skip
686                elseif di.content then
687                    m = m + 1
688                    d[m] = di
689                else
690                    local dd = di.data
691                    if #dd > 0 and checked(dd) > 0 then
692                        m = m + 1
693                        d[m] = di
694                    end
695                end
696            end
697            if m < n then
698                for i=n,m+1,-1 do
699                    d[i] = nil
700                end
701            end
702            return m
703        end
704    end
705
706    function checks.mrow(di)
707     -- local d = di.data
708     -- if d then
709     --     checked(d)
710     -- end
711    end
712
713    -- we can move more checks here
714
715    local function flatten(di)
716        local r = di.__p__
717        while r do
718            local d = r.data
719            local n = #d
720            if d and n > 1 then
721                n = checked(d)
722            end
723            local tg = r.tg
724            if n == 1 and (tg == "mtext" or tg == "mrow") then
725                r.skip = "comment" -- weird error
726                r = r.__p__
727            else
728                break
729            end
730        end
731    end
732
733    function checks.mtable(di)
734        flatten(di)
735        local d = di.data
736        for i=1,#d do
737            local d = d[i]
738            if d.tg == "mtr" then
739                local d = d.data
740                for i=1,#d do
741                    local d = d[i]
742                    if d.tg == "mtd" then
743                        -- okay
744                    elseif d.content then
745                        d.content = ""
746                    else
747                        d.skip = "comment" -- weird error
748                    end
749                end
750            elseif d.content then
751                d.content = ""
752            else
753                d.skip = "comment" -- weird error
754            end
755        end
756    end
757
758    do
759
760        local a, z, A, Z = 0x61, 0x7A, 0x41, 0x5A
761
762        function extras.mi(di,element,n,fulltag) -- check with content
763            local str = di.data[1].content
764            if str and sub(str,1,1) ~= "&" then -- hack but good enough (maybe gsub op eerste)
765                for v in utfvalues(str) do
766                    if (v >= a and v <= z) or (v >= A and v <= Z) then
767                        local a = di.attributes
768                        if a then
769                            a.mathvariant = "normal"
770                        else
771                            di.attributes = { mathvariant = "normal" }
772                        end
773                    end
774                end
775            end
776        end
777
778    end
779
780    function extras.msub(di,element,n,fulltag)
781        -- m$^2$
782        local data = di.data
783        if #data == 1 then
784            local d = data[1]
785            data[2] = d
786            d.__i__ = 2
787            data[1] = dummy_nucleus
788        end
789    end
790
791    extras.msup = extras.msub
792
793end
794