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

sile-typesetter / sile / 9304191631

30 May 2024 02:21PM UTC coverage: 49.894% (-10.8%) from 60.669%
9304191631

push

github

web-flow
Merge fcc56c666 into 1a26b4f22

8447 of 16930 relevant lines covered (49.89%)

1187.3 hits per line

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

73.76
/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")
2✔
3
local syms = require("packages.math.unicode-symbols")
2✔
4

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

22
local ConvertMathML
23

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

35
-- convert MathML into mbox
36
function ConvertMathML (_, content)
2✔
37
   if content == nil or content.command == nil then
468✔
38
      return nil
174✔
39
   end
40
   if content.command == "math" or content.command == "mathml" then -- toplevel
294✔
41
      return b.stackbox("V", convertChildren(content))
20✔
42
   elseif content.command == "mrow" then
284✔
43
      return b.stackbox("H", convertChildren(content))
32✔
44
   elseif content.command == "mi" then
268✔
45
      local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
60✔
46
      local text = content[1]
60✔
47
      if type(text) ~= "string" then
60✔
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)
60✔
51
      return b.text("identifier", {}, script, text)
60✔
52
   elseif content.command == "mo" then
208✔
53
      local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
96✔
54
         or b.scriptType.upright
96✔
55
      local text = content[1]
96✔
56
      local attributes = {}
96✔
57
      if syms.symbolDefaults[text] then
96✔
58
         for attribute, value in pairs(syms.symbolDefaults[text]) do
216✔
59
            attributes[attribute] = value
126✔
60
         end
61
      end
62
      if content.options.atom then
96✔
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
96✔
70
         SU.error("mo command contains " .. text .. ", which is not text")
×
71
      end
72
      return b.text("operator", attributes, script, text)
96✔
73
   elseif content.command == "mn" then
112✔
74
      local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
38✔
75
         or b.scriptType.upright
38✔
76
      local text = content[1]
38✔
77
      if type(text) ~= "string" then
38✔
78
         SU.error("mn command contains " .. text .. ", which is not text")
×
79
      end
80
      if string.sub(text, 1, 1) == "-" then
76✔
81
         text = "−" .. string.sub(text, 2)
×
82
      end
83
      return b.text("number", {}, script, text)
38✔
84
   elseif content.command == "mspace" then
74✔
85
      return b.space(content.options.width, content.options.height, content.options.depth)
×
86
   elseif content.command == "msub" then
74✔
87
      local children = convertChildren(content)
10✔
88
      if #children ~= 2 then
10✔
89
         SU.error("Wrong number of children in msub")
×
90
      end
91
      return b.newSubscript({ base = children[1], sub = children[2] })
10✔
92
   elseif content.command == "msup" then
64✔
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
52✔
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
52✔
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
52✔
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
52✔
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
52✔
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
52✔
129
      local children = convertChildren(content)
4✔
130
      return b.table(children, content.options)
4✔
131
   elseif content.command == "mtr" then
48✔
132
      return b.mtr(convertChildren(content))
24✔
133
   elseif content.command == "mtd" then
36✔
134
      return b.stackbox("H", convertChildren(content))
72✔
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"
10✔
142
   local counter = SU.boolean(options.numbered, false) and "equation"
20✔
143
   counter = options.counter or counter -- overrides the default "equation" counter
10✔
144

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

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

159
   if mode == "display" then
10✔
160
      SILE.typesetter:endline()
5✔
161
      SILE.typesetter:pushExplicitVglue(SILE.settings:get("math.displayskip"))
10✔
162
      SILE.settings:temporarily(function ()
10✔
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()
10✔
166
         local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue()
10✔
167
         SILE.settings:set("document.parindent", SILE.types.node.glue())
10✔
168
         SILE.settings:set("current.parindent", SILE.types.node.glue())
10✔
169
         SILE.settings:set("document.lskip", SILE.types.node.hfillglue(lskip.width.length))
10✔
170
         SILE.settings:set("document.rskip", SILE.types.node.glue(rskip.width.length))
10✔
171
         SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue())
10✔
172
         SILE.settings:set("document.spaceskip", SILE.types.length("1spc", 0, 0))
10✔
173
         SILE.typesetter:pushHorizontal(mbox)
5✔
174
         SILE.typesetter:pushExplicitGlue(SILE.nodefactory.hfillglue())
15✔
175
         if counter then
5✔
176
            options.counter = counter
×
177
            SILE.call("increment-counter", { id = counter })
×
178
            SILE.call("math:numberingstyle", options)
×
179
         elseif options.number then
5✔
180
            SILE.call("math:numberingstyle", options)
×
181
         end
182
         SILE.typesetter:endline()
5✔
183
      end)
184
      SILE.typesetter:pushExplicitVglue(SILE.settings:get("math.displayskip"))
15✔
185
   else
186
      SILE.typesetter:pushHorizontal(mbox)
5✔
187
   end
188
end
189

190
return { ConvertMathML, handleMath }
2✔
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