Skip to content

Commit

Permalink
wip(math): Add missing MathML mathvariant mappings
Browse files Browse the repository at this point in the history
  • Loading branch information
Omikhleia authored and Didier Willis committed Oct 23, 2024
1 parent ef1d8a6 commit d023fc4
Show file tree
Hide file tree
Showing 4 changed files with 455 additions and 88 deletions.
99 changes: 99 additions & 0 deletions math-showcase/mathvariant-tables.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
-- "normal" | "bold" | "italic" | "bold-italic" | "double-struck" | "bold-fraktur" | "script" | "bold-script" | "fraktur" | "sans-serif" | "bold-sans-serif" | "sans-serif-italic" | "sans-serif-bold-italic" | "monospace" | "initial" | "tailed" | "looped" | "stretched"
-- For numbers we only have "normal" "bold" monospace sans-serif double-struck and sans-serif-bold
local letter_variants = {
"normal",
"bold",
"italic",
"bold-italic",
"double-struck",
"fraktur",
"bold-fraktur",
"script",
"bold-script",
"sans-serif",
"bold-sans-serif",
"sans-serif-italic",
"sans-serif-bold-italic",
"monospace",
-- "initial",
-- "tailed",
-- "looped",
-- "stretched"
}
local number_variants = {
"normal",
"bold",
"sans-serif",
"bold-sans-serif",
"double-struck",
"monospace",
}


local greek_variants = {
"normal",
"bold",
"italic",
"bold-italic",
"bold-sans-serif",
}

local function mathvariant(variant, char)
return ('<mathml><mo mathvariant="%s">%s</mo></mathml>'):format(variant, char)
end

local function build_table(start, stop, variants)
local t = {}
local ncols = #variants
local szcols = 1 / ncols * 99 .. "%lw"

local header = {}
for _, variant in ipairs(variants) do
local cell = SU.ast.createCommand("cell", {
halign = "left",
}, { variant })
table.insert(header, cell)
end
-- table.insert(t, SU.ast.createStructuredCommand("row", {
-- background = "#f0f0f0"
-- }, header))

for letter = start, stop do
if letter ~= 0x3A2 then -- invalid capital greek letter
local row = {}
local char = luautf8.char(letter)
for _, variant in ipairs(variants) do
local cell = SU.ast.createCommand("cell", {
halign = "center",
}, function ()
return SILE.processString(mathvariant(variant, char), "xml")
end)
table.insert(row, cell)
end
local row = SU.ast.createStructuredCommand("row", {}, row)
table.insert(t, row)
end
end
t = SU.ast.createStructuredCommand("ptable", {
cols = string.rep(szcols, ncols, " "),
-- header = true
}, t)
return t
end

SILE.call("par")
SILE.call("noindent")
local fontSz = SILE.settings:get("font.size")
local mathSz = SILE.settings:get("math.font.size")
SILE.call("font", { size = "0.6em" })
SILE.settings:set("math.font.size", SILE.settings:get("font.size"))
SILE.process({ build_table(0x41, 0x41 + 25, letter_variants) })
SILE.process({ build_table(0x61, 0x61 + 25, letter_variants) })
SILE.process({ build_table(0x30, 0x30 + 9, number_variants) })
-- greek upper
SILE.process({ build_table(0x391, 0x391 + 24, greek_variants) })
-- greek lower
SILE.process({ build_table(0x3b1, 0x3b1 + 24, greek_variants) })
SILE.call("font", { size = fontSz })
SILE.settings:set("math.font.size", mathSz)
SILE.call("indent")
84 changes: 3 additions & 81 deletions packages/math/base-elements.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ local nodefactory = require("types.node")
local hb = require("justenoughharfbuzz")
local ot = require("core.opentype-parser")
local syms = require("packages.math.unicode-symbols")
local mathvariants = require("packages.math.unicode-mathvariants")
local convertMathVariantScript = mathvariants.convertMathVariantScript

local atomType = syms.atomType
local symbolDefaults = syms.symbolDefaults
Expand All @@ -19,32 +21,6 @@ local mathMode = {
scriptScriptCramped = 7,
}

local scriptType = {
upright = 1,
bold = 2, -- also have Greek and digits
italic = 3, -- also have Greek
boldItalic = 4, -- also have Greek
script = 5,
boldScript = 6,
fraktur = 7,
boldFraktur = 8,
doubleStruck = 9, -- also have digits
sansSerif = 10, -- also have digits
sansSerifBold = 11, -- also have Greek and digits
sansSerifItalic = 12,
sansSerifBoldItalic = 13, -- also have Greek
monospace = 14, -- also have digits
}

local mathVariantToScriptType = function (attr)
return attr == "normal" and scriptType.upright
or attr == "bold" and scriptType.bold
or attr == "italic" and scriptType.italic
or attr == "bold-italic" and scriptType.boldItalic
or attr == "double-struck" and scriptType.doubleStruck
or SU.error('Invalid value "' .. attr .. '" for option mathvariant')
end

local function isDisplayMode (mode)
return mode <= 1
end
Expand All @@ -61,49 +37,6 @@ local function isScriptScriptMode (mode)
return mode == mathMode.scriptScript or mode == mathMode.scriptScriptCramped
end

local mathScriptConversionTable = {
capital = {
[scriptType.upright] = function (codepoint)
return codepoint
end,
[scriptType.bold] = function (codepoint)
return codepoint + 0x1D400 - 0x41
end,
[scriptType.italic] = function (codepoint)
return codepoint + 0x1D434 - 0x41
end,
[scriptType.boldItalic] = function (codepoint)
return codepoint + 0x1D468 - 0x41
end,
[scriptType.doubleStruck] = function (codepoint)
return codepoint == 0x43 and 0x2102
or codepoint == 0x48 and 0x210D
or codepoint == 0x4E and 0x2115
or codepoint == 0x50 and 0x2119
or codepoint == 0x51 and 0x211A
or codepoint == 0x52 and 0x211D
or codepoint == 0x5A and 0x2124
or codepoint + 0x1D538 - 0x41
end,
},
small = {
[scriptType.upright] = function (codepoint)
return codepoint
end,
[scriptType.bold] = function (codepoint)
return codepoint + 0x1D41A - 0x61
end,
[scriptType.italic] = function (codepoint)
return codepoint == 0x68 and 0x210E or codepoint + 0x1D44E - 0x61
end,
[scriptType.boldItalic] = function (codepoint)
return codepoint + 0x1D482 - 0x61
end,
[scriptType.doubleStruck] = function (codepoint)
return codepoint + 0x1D552 - 0x61
end,
},
}

local mathCache = {}

Expand Down Expand Up @@ -952,16 +885,7 @@ function elements.text:_init (kind, attributes, script, text)
self.script = script
self.text = text
if self.script ~= "upright" then
local converted = ""
for _, uchr in luautf8.codes(self.text) do
local dst_char = luautf8.char(uchr)
if uchr >= 0x41 and uchr <= 0x5A then -- Latin capital letter
dst_char = luautf8.char(mathScriptConversionTable.capital[self.script](uchr))
elseif uchr >= 0x61 and uchr <= 0x7A then -- Latin non-capital letter
dst_char = luautf8.char(mathScriptConversionTable.small[self.script](uchr))
end
converted = converted .. dst_char
end
local converted = convertMathVariantScript(self.text, self.script)
self.originalText = self.text
self.text = converted
end
Expand Down Expand Up @@ -1516,8 +1440,6 @@ end

elements.mathMode = mathMode
elements.atomType = atomType
elements.scriptType = scriptType
elements.mathVariantToScriptType = mathVariantToScriptType
elements.symbolDefaults = symbolDefaults
elements.newSubscript = newSubscript
elements.newUnderOver = newUnderOver
Expand Down
16 changes: 9 additions & 7 deletions packages/math/typesetter.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
-- Interpret a MathML or TeX-like AST, typeset it and add it to the output.
local b = require("packages.math.base-elements")
local syms = require("packages.math.unicode-symbols")
local mathvariants = require("packages.math.unicode-mathvariants")
local mathVariantToScriptType, scriptType = mathvariants.mathVariantToScriptType, mathvariants.scriptType

-- Shorthands for atom types, used in the `atom` command option
local atomTypeShort = {
Expand Down Expand Up @@ -47,16 +49,16 @@ function ConvertMathML (_, content)
local special = content.options.special
return b.phantom(convertChildren(content), special)
elseif content.command == "mi" then
local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
local script = content.options.mathvariant and mathVariantToScriptType(content.options.mathvariant)
local text = content[1]
if type(text) ~= "string" then
SU.error("mi command contains content which is not text")
end
script = script or (luautf8.len(text) == 1 and b.scriptType.italic or b.scriptType.upright)
script = script or (luautf8.len(text) == 1 and scriptType.italic or scriptType.upright)
return b.text("identifier", {}, script, text)
elseif content.command == "mo" then
local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
or b.scriptType.upright
local script = content.options.mathvariant and mathVariantToScriptType(content.options.mathvariant)
or scriptType.upright
local text = content[1]
local attributes = {}
if syms.symbolDefaults[text] then
Expand All @@ -76,8 +78,8 @@ function ConvertMathML (_, content)
end
return b.text("operator", attributes, script, text)
elseif content.command == "mn" then
local script = content.options.mathvariant and b.mathVariantToScriptType(content.options.mathvariant)
or b.scriptType.upright
local script = content.options.mathvariant and mathVariantToScriptType(content.options.mathvariant)
or scriptType.upright
local text = content[1]
if type(text) ~= "string" then
SU.error("mn command contains content which is not text")
Expand Down Expand Up @@ -156,7 +158,7 @@ function ConvertMathML (_, content)
-- and soft wrap opportunities: ignored here.
-- There's also some explanations about CSS, italic correction etc. which we ignore too.
text = text:gsub("[\n\r]", " ")
return b.text("string", {}, b.scriptType.upright, text:gsub("%s+", " "))
return b.text("string", {}, scriptType.upright, text:gsub("%s+", " "))
else
SU.error("Unknown math command " .. content.command)
end
Expand Down
Loading

0 comments on commit d023fc4

Please sign in to comment.