• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

sile-typesetter / sile / 9441244789

10 Jun 2024 01:45AM UTC coverage: 49.048% (-11.6%) from 60.675%
9441244789

push

github

web-flow
Merge 8a4989eae into 519864108

8320 of 16963 relevant lines covered (49.05%)

1813.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.12
/packages/math/typesetter.lua
1
-- Interpret a MathML or TeX-like AST, typeset it and add it to the output.
2
local b = require("packages.math.base-elements")
1✔
3
local syms = require("packages.math.unicode-symbols")
1✔
4

5
-- Shorthands for atom types, used in the `atom` command option
6
local atomTypeShort = {
1✔
7
   ord = b.atomType.ordinary,
1✔
8
   big = b.atomType.bigOperator,
1✔
9
   bin = b.atomType.binaryOperator,
1✔
10
   rel = b.atomType.relationalOperator,
1✔
11
   open = b.atomType.openingSymbol,
1✔
12
   close = b.atomType.closeSymbol,
1✔
13
   punct = b.atomType.punctuationSymbol,
1✔
14
   inner = b.atomType.inner,
1✔
15
   over = b.atomType.overSymbol,
1✔
16
   under = b.atomType.underSymbol,
1✔
17
   accent = b.atomType.accentSymbol,
1✔
18
   radical = b.atomType.radicalSymbol,
1✔
19
   vcenter = b.atomType.vcenter,
1✔
20
}
21

22
local ConvertMathML
23

24
local function convertChildren (tree)
25
   local mboxes = {}
30✔
26
   for _, n in ipairs(tree) do
268✔
27
      local box = ConvertMathML(nil, n)
238✔
28
      if box then
238✔
29
         table.insert(mboxes, box)
150✔
30
      end
31
   end
32
   return mboxes
30✔
33
end
34

35
-- convert MathML into mbox
36
function ConvertMathML (_, content)
1✔
37
   if content == nil or content.command == nil then
244✔
38
      return nil
88✔
39
   end
40
   if content.command == "math" or content.command == "mathml" then -- toplevel
156✔
41
      return b.stackbox("V", convertChildren(content))
12✔
42
   elseif content.command == "mrow" then
150✔
43
      return b.stackbox("H", convertChildren(content))
24✔
44
   elseif content.command == "mi" then
138✔
45
      local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
42✔
46
      local text = content[1]
42✔
47
      if type(text) ~= "string" then
42✔
48
         SU.error("mi command contains " .. text .. ", which is not text")
×
49
      end
50
      script = script or (luautf8.len(text) == 1 and b.scriptType.italic or b.scriptType.upright)
42✔
51
      return b.text("identifier", {}, script, text)
42✔
52
   elseif content.command == "mo" then
96✔
53
      local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
78✔
54
         or b.scriptType.upright
78✔
55
      local text = content[1]
78✔
56
      local attributes = {}
78✔
57
      if syms.symbolDefaults[text] then
78✔
58
         for attribute, value in pairs(syms.symbolDefaults[text]) do
180✔
59
            attributes[attribute] = value
108✔
60
         end
61
      end
62
      if content.options.atom then
78✔
63
         if not atomTypeShort[content.options.atom] then
×
64
            SU.error("Unknown atom type " .. content.options.atom)
×
65
         else
66
            attributes.atom = atomTypeShort[content.options.atom]
×
67
         end
68
      end
69
      if type(text) ~= "string" then
78✔
70
         SU.error("mo command contains " .. text .. ", which is not text")
×
71
      end
72
      return b.text("operator", attributes, script, text)
78✔
73
   elseif content.command == "mn" then
18✔
74
      local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
6✔
75
         or b.scriptType.upright
6✔
76
      local text = content[1]
6✔
77
      if type(text) ~= "string" then
6✔
78
         SU.error("mn command contains " .. text .. ", which is not text")
×
79
      end
80
      if string.sub(text, 1, 1) == "-" then
12✔
81
         text = "−" .. string.sub(text, 2)
×
82
      end
83
      return b.text("number", {}, script, text)
6✔
84
   elseif content.command == "mspace" then
12✔
85
      return b.space(content.options.width, content.options.height, content.options.depth)
×
86
   elseif content.command == "msub" then
12✔
87
      local children = convertChildren(content)
×
88
      if #children ~= 2 then
×
89
         SU.error("Wrong number of children in msub")
×
90
      end
91
      return b.newSubscript({ base = children[1], sub = children[2] })
×
92
   elseif content.command == "msup" then
12✔
93
      local children = convertChildren(content)
12✔
94
      if #children ~= 2 then
12✔
95
         SU.error("Wrong number of children in msup")
×
96
      end
97
      return b.newSubscript({ base = children[1], sup = children[2] })
12✔
98
   elseif content.command == "msubsup" then
×
99
      local children = convertChildren(content)
×
100
      if #children ~= 3 then
×
101
         SU.error("Wrong number of children in msubsup")
×
102
      end
103
      return b.newSubscript({ base = children[1], sub = children[2], sup = children[3] })
×
104
   elseif content.command == "munder" then
×
105
      local children = convertChildren(content)
×
106
      if #children ~= 2 then
×
107
         SU.error("Wrong number of children in munder")
×
108
      end
109
      return b.newUnderOver({ base = children[1], sub = children[2] })
×
110
   elseif content.command == "mover" then
×
111
      local children = convertChildren(content)
×
112
      if #children ~= 2 then
×
113
         SU.error("Wrong number of children in mover")
×
114
      end
115
      return b.newUnderOver({ base = children[1], sup = children[2] })
×
116
   elseif content.command == "munderover" then
×
117
      local children = convertChildren(content)
×
118
      if #children ~= 3 then
×
119
         SU.error("Wrong number of children in munderover")
×
120
      end
121
      return b.newUnderOver({ base = children[1], sub = children[2], sup = children[3] })
×
122
   elseif content.command == "mfrac" then
×
123
      local children = convertChildren(content)
×
124
      if #children ~= 2 then
×
125
         SU.error("Wrong number of children in mfrac: " .. #children)
×
126
      end
127
      return b.fraction(children[1], children[2])
×
128
   elseif content.command == "mtable" or content.command == "table" then
×
129
      local children = convertChildren(content)
×
130
      return b.table(children, content.options)
×
131
   elseif content.command == "mtr" then
×
132
      return b.mtr(convertChildren(content))
×
133
   elseif content.command == "mtd" then
×
134
      return b.stackbox("H", convertChildren(content))
×
135
   else
136
      SU.error("Unknown math command " .. content.command)
×
137
   end
138
end
139

140
local function handleMath (_, mbox, options)
141
   local mode = options and options.mode or "text"
6✔
142
   local counter = SU.boolean(options.numbered, false) and "equation"
12✔
143
   counter = options.counter or counter -- overrides the default "equation" counter
6✔
144

145
   if mode == "display" then
6✔
146
      mbox.mode = b.mathMode.display
3✔
147
   elseif mode == "text" then
3✔
148
      mbox.mode = b.mathMode.textCramped
3✔
149
   else
150
      SU.error("Unknown math mode " .. mode)
×
151
   end
152

153
   SU.debug("math", function ()
12✔
154
      return "Resulting mbox: " .. tostring(mbox)
×
155
   end)
156
   mbox:styleDescendants()
6✔
157
   mbox:shapeTree()
6✔
158

159
   if mode == "display" then
6✔
160
      SILE.typesetter:endline()
3✔
161
      SILE.typesetter:pushExplicitVglue(SILE.settings:get("math.displayskip"))
6✔
162
      SILE.settings:temporarily(function ()
6✔
163
         -- Center the equation in the space available up to the counter (if any),
164
         -- respecting the fixed part of the left and right skips.
165
         local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue()
6✔
166
         local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue()
6✔
167
         SILE.settings:set("document.parindent", SILE.types.node.glue())
6✔
168
         SILE.settings:set("current.parindent", SILE.types.node.glue())
6✔
169
         SILE.settings:set("document.lskip", SILE.types.node.hfillglue(lskip.width.length))
6✔
170
         SILE.settings:set("document.rskip", SILE.types.node.glue(rskip.width.length))
6✔
171
         SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue())
6✔
172
         SILE.settings:set("document.spaceskip", SILE.types.length("1spc", 0, 0))
6✔
173
         SILE.typesetter:pushHorizontal(mbox)
3✔
174
         SILE.typesetter:pushExplicitGlue(SILE.types.node.hfillglue())
6✔
175
         if counter then
3✔
176
            options.counter = counter
×
177
            SILE.call("increment-counter", { id = counter })
×
178
            SILE.call("math:numberingstyle", options)
×
179
         elseif options.number then
3✔
180
            SILE.call("math:numberingstyle", options)
×
181
         end
182
         SILE.typesetter:endline()
3✔
183
      end)
184
      SILE.typesetter:pushExplicitVglue(SILE.settings:get("math.displayskip"))
9✔
185
   else
186
      SILE.typesetter:pushHorizontal(mbox)
3✔
187
   end
188
end
189

190
return { ConvertMathML, handleMath }
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc