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

sile-typesetter / sile / 11642878293

02 Nov 2024 12:56PM UTC coverage: 69.34% (+3.7%) from 65.595%
11642878293

push

github

alerque
chore(deps): Bump pinned versions of patch level crate updates

12579 of 18141 relevant lines covered (69.34%)

6066.76 hits per line

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

80.63
/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")
13✔
3
local syms = require("packages.math.unicode-symbols")
13✔
4
local mathvariants = require("packages.math.unicode-mathvariants")
13✔
5
local mathVariantToScriptType, scriptType = mathvariants.mathVariantToScriptType, mathvariants.scriptType
13✔
6

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

24
local ConvertMathML
25

26
local function convertChildren (tree)
27
   local mboxes = {}
592✔
28
   for _, n in ipairs(tree) do
2,707✔
29
      local box = ConvertMathML(nil, n)
2,115✔
30
      if box then
2,115✔
31
         table.insert(mboxes, box)
1,575✔
32
      end
33
   end
34
   return mboxes
592✔
35
end
36

37
-- convert MathML into mbox
38
function ConvertMathML (_, content)
13✔
39
   if content == nil or content.command == nil then
2,187✔
40
      return nil
540✔
41
   end
42
   if content.command == "math" or content.command == "mathml" then -- toplevel
1,647✔
43
      return b.stackbox("V", convertChildren(content))
144✔
44
   elseif content.command == "mrow" then
1,575✔
45
      return b.stackbox("H", convertChildren(content))
582✔
46
   elseif content.command == "mphantom" then
1,284✔
47
      -- MathML's standard mphantom corresponds to TeX's \phantom only.
48
      -- Let's support a special attribute "h" or "v" for TeX-like \hphantom or \vphantom.
49
      local special = content.options.special
×
50
      return b.phantom(convertChildren(content), special)
×
51
   elseif content.command == "mi" then
1,284✔
52
      local script = content.options.mathvariant and mathVariantToScriptType(content.options.mathvariant)
384✔
53
      local text = content[1]
384✔
54
      if type(text) ~= "string" then
384✔
55
         SU.error("mi command contains content which is not text")
×
56
      end
57
      script = script or (luautf8.len(text) == 1 and scriptType.italic or scriptType.upright)
384✔
58
      return b.text("identifier", {}, script, text)
384✔
59
   elseif content.command == "mo" then
900✔
60
      local script = content.options.mathvariant and mathVariantToScriptType(content.options.mathvariant)
394✔
61
         or scriptType.upright
394✔
62
      local text = content[1]
394✔
63
      local attributes = {}
394✔
64
      if syms.symbolDefaults[text] then
394✔
65
         for attribute, value in pairs(syms.symbolDefaults[text]) do
812✔
66
            attributes[attribute] = value
458✔
67
         end
68
      end
69
      if content.options.atom then
394✔
70
         if not atomTypeShort[content.options.atom] then
6✔
71
            SU.error("Unknown atom type " .. content.options.atom)
×
72
         else
73
            attributes.atom = atomTypeShort[content.options.atom]
6✔
74
         end
75
      end
76
      if type(text) ~= "string" then
394✔
77
         SU.error("mo command contains content which is not text")
×
78
      end
79
      return b.text("operator", attributes, script, text)
394✔
80
   elseif content.command == "mn" then
506✔
81
      local script = content.options.mathvariant and mathVariantToScriptType(content.options.mathvariant)
219✔
82
         or scriptType.upright
219✔
83
      local text = content[1]
219✔
84
      if type(text) ~= "string" then
219✔
85
         SU.error("mn command contains content which is not text")
×
86
      end
87
      if string.sub(text, 1, 1) == "-" then
438✔
88
         text = "−" .. string.sub(text, 2)
2✔
89
      end
90
      return b.text("number", {}, script, text)
219✔
91
   elseif content.command == "mspace" then
287✔
92
      return b.space(content.options.width, content.options.height, content.options.depth)
58✔
93
   elseif content.command == "msub" then
229✔
94
      local children = convertChildren(content)
63✔
95
      if #children ~= 2 then
63✔
96
         SU.error("Wrong number of children in msub")
×
97
      end
98
      return b.newSubscript({ base = children[1], sub = children[2] })
63✔
99
   elseif content.command == "msup" then
166✔
100
      local children = convertChildren(content)
37✔
101
      if #children ~= 2 then
37✔
102
         SU.error("Wrong number of children in msup")
×
103
      end
104
      return b.newSubscript({ base = children[1], sup = children[2] })
37✔
105
   elseif content.command == "msubsup" then
129✔
106
      local children = convertChildren(content)
14✔
107
      if #children ~= 3 then
14✔
108
         SU.error("Wrong number of children in msubsup")
×
109
      end
110
      return b.newSubscript({ base = children[1], sub = children[2], sup = children[3] })
14✔
111
   elseif content.command == "munder" then
115✔
112
      local children = convertChildren(content)
1✔
113
      if #children ~= 2 then
1✔
114
         SU.error("Wrong number of children in munder")
×
115
      end
116
      return b.newUnderOver({ base = children[1], sub = children[2] })
1✔
117
   elseif content.command == "mover" then
114✔
118
      local children = convertChildren(content)
×
119
      if #children ~= 2 then
×
120
         SU.error("Wrong number of children in mover")
×
121
      end
122
      return b.newUnderOver({ base = children[1], sup = children[2] })
×
123
   elseif content.command == "munderover" then
114✔
124
      local children = convertChildren(content)
24✔
125
      if #children ~= 3 then
24✔
126
         SU.error("Wrong number of children in munderover")
×
127
      end
128
      return b.newUnderOver({ base = children[1], sub = children[2], sup = children[3] })
24✔
129
   elseif content.command == "mfrac" then
90✔
130
      local children = convertChildren(content)
33✔
131
      if #children ~= 2 then
33✔
132
         SU.error("Wrong number of children in mfrac: " .. #children)
×
133
      end
134
      return b.fraction(children[1], children[2])
33✔
135
   elseif content.command == "msqrt" then
57✔
136
      local children = convertChildren(content)
×
137
      -- "The <msqrt> element generates an anonymous <mrow> box called the msqrt base
138
      return b.sqrt(b.stackbox("H", children))
×
139
   elseif content.command == "mroot" then
57✔
140
      local children = convertChildren(content)
×
141
      return b.sqrt(children[1], children[2])
×
142
   elseif content.command == "mtable" or content.command == "table" then
57✔
143
      local children = convertChildren(content)
9✔
144
      return b.table(children, content.options)
9✔
145
   elseif content.command == "mtr" then
48✔
146
      return b.mtr(convertChildren(content))
24✔
147
   elseif content.command == "mtd" then
36✔
148
      return b.stackbox("H", convertChildren(content))
72✔
149
   elseif content.command == "mtext" or content.command == "ms" then
×
150
      if #content > 1 then
×
151
         SU.error("Wrong number of children in " .. content.command .. ": " .. #content)
×
152
      end
153
      local text = content[1] or "" -- empty mtext is allowed, and found in examples...
×
154
      if type(text) ~= "string" then
×
155
         SU.error(content.command .. " command contains content which is not text")
×
156
      end
157
      -- MathML Core 3.2.1.1 Layout of <mtext> has some wording about forced line breaks
158
      -- and soft wrap opportunities: ignored here.
159
      -- There's also some explanations about CSS, italic correction etc. which we ignore too.
160
      text = text:gsub("[\n\r]", " ")
×
161
      return b.text("string", {}, scriptType.upright, text:gsub("%s+", " "))
×
162
   else
163
      SU.error("Unknown math command " .. content.command)
×
164
   end
165
end
166

167
local function handleMath (_, mbox, options)
168
   local mode = options and options.mode or "text"
72✔
169
   local counter = SU.boolean(options.numbered, false) and "equation"
144✔
170
   counter = options.counter or counter -- overrides the default "equation" counter
72✔
171

172
   if mode == "display" then
72✔
173
      mbox.mode = b.mathMode.display
44✔
174
   elseif mode == "text" then
28✔
175
      mbox.mode = b.mathMode.textCramped
28✔
176
   else
177
      SU.error("Unknown math mode " .. mode)
×
178
   end
179

180
   SU.debug("math", function ()
144✔
181
      return "Resulting mbox: " .. tostring(mbox)
×
182
   end)
183
   mbox:styleDescendants()
72✔
184
   mbox:shapeTree()
72✔
185

186
   if mode == "display" then
72✔
187
      SILE.typesetter:endline()
44✔
188
      SILE.typesetter:pushExplicitVglue(SILE.settings:get("math.displayskip"))
88✔
189
      SILE.settings:temporarily(function ()
88✔
190
         -- Center the equation in the space available up to the counter (if any),
191
         -- respecting the fixed part of the left and right skips.
192
         local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue()
88✔
193
         local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue()
88✔
194
         SILE.settings:set("document.parindent", SILE.types.node.glue())
88✔
195
         SILE.settings:set("current.parindent", SILE.types.node.glue())
88✔
196
         SILE.settings:set("document.lskip", SILE.types.node.hfillglue(lskip.width.length))
88✔
197
         SILE.settings:set("document.rskip", SILE.types.node.glue(rskip.width.length))
88✔
198
         SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue())
88✔
199
         SILE.settings:set("document.spaceskip", SILE.types.length("1spc", 0, 0))
88✔
200
         SILE.typesetter:pushHorizontal(mbox)
44✔
201
         SILE.typesetter:pushExplicitGlue(SILE.types.node.hfillglue())
88✔
202
         if counter then
44✔
203
            options.counter = counter
5✔
204
            SILE.call("increment-counter", { id = counter })
5✔
205
            SILE.call("math:numberingstyle", options)
10✔
206
         elseif options.number then
39✔
207
            SILE.call("math:numberingstyle", options)
1✔
208
         end
209
         SILE.typesetter:endline()
44✔
210
      end)
211
      SILE.typesetter:pushExplicitVglue(SILE.settings:get("math.displayskip"))
132✔
212
   else
213
      SILE.typesetter:pushHorizontal(mbox)
28✔
214
   end
215
end
216

217
return { ConvertMathML, handleMath }
13✔
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