2021-06-01 22:29:48 +02:00
|
|
|
require'pdfmml-emulate-node'
|
|
|
|
|
|
|
|
local properties = node.get_properties_table()
|
|
|
|
|
2021-05-28 18:25:25 +02:00
|
|
|
local l = lpeg or require'lpeg'
|
|
|
|
local hex_digit = l.R('09', 'af')
|
|
|
|
local function hex_to_int(s) return tonumber(s, 16) end
|
|
|
|
local tex_char = l.Cg('^^' * (hex_digit * hex_digit / hex_to_int
|
|
|
|
+ l.R'\0\x3F' / function(s) return s:byte() + 0x40 end
|
|
|
|
+ l.R'\x40\x7F' / function(s) return s:byte() - 0x40 end)
|
|
|
|
+ l.P(1) / string.byte)
|
|
|
|
|
2021-05-31 13:25:08 +02:00
|
|
|
local scaled = l.P'-'^-1 * l.R'09'^1 * '.' * l.R'09'^1 / function(s) return (tonumber(s * 0x10000) + .5) // 1 end
|
2021-07-03 22:38:32 +02:00
|
|
|
local int = l.P'-'^-1 * l.R'09'^1 / tonumber
|
|
|
|
local glue_order_mu = 'filll' * l.Cc(3)
|
|
|
|
+ 'fill' * l.Cc(2)
|
|
|
|
+ 'fil' * l.Cc(1)
|
|
|
|
+ 'mu' * l.Cc(0)
|
|
|
|
local glue_order_pt = 'filll' * l.Cc(3)
|
|
|
|
+ 'fill' * l.Cc(2)
|
|
|
|
+ 'fil' * l.Cc(1)
|
|
|
|
+ 'pt' * l.Cc(0)
|
|
|
|
local glue_order = 'filll' * l.Cc(3)
|
|
|
|
+ 'fill' * l.Cc(2)
|
|
|
|
+ 'fil' * l.Cc(1)
|
|
|
|
+ l.Cc(0)
|
2021-05-28 18:25:25 +02:00
|
|
|
local delimiter_code = '"' * (l.R('09', 'AF')^1 / function(s)
|
|
|
|
local code = tonumber(s, 16)
|
|
|
|
return {id = 'delim',
|
|
|
|
small_fam = (code >> 20) & 0xF,
|
2021-05-29 12:39:24 +02:00
|
|
|
small_char = (code >> 12) & 0xFF,
|
|
|
|
large_fam = (code >> 8) & 0xF,
|
2021-05-28 18:25:25 +02:00
|
|
|
large_char = code & 0xFF,
|
|
|
|
}
|
|
|
|
end)
|
|
|
|
|
2021-07-03 22:38:32 +02:00
|
|
|
local balanced_braces = l.Ct{'{' * (1-l.S'{}'+l.V(1))^0 * '}'}
|
|
|
|
|
2021-05-28 18:25:25 +02:00
|
|
|
local math_char = l.Ct('\\fam' * l.Cg(l.R'09'^1 / tonumber, 'fam') * ' ' * l.Cg(tex_char, 'char') * l.Cg(l.Cc'math_char', 'id'))
|
|
|
|
|
2021-07-03 22:38:32 +02:00
|
|
|
local hdw = '(' * l.Cg(scaled + '*' * l.Cc(-0x40000000), 'height') * '+' * l.Cg(scaled + '*' * l.Cc(-0x40000000), 'depth') * ')x' * l.Cg(scaled + '*' * l.Cc(-0x40000000), 'width')
|
|
|
|
|
2021-07-04 03:18:36 +02:00
|
|
|
local generic_simple_node = l.Ct('\\' * (
|
|
|
|
l.Cg('rule', 'id') * hdw
|
|
|
|
+ l.Cg('kern', 'id') * l.Cg(' ' * l.Cc(1) + l.Cc(0), 'subtype') * l.Cg(scaled, 'kern') * (' (for ' * (l.R'az' + l.S'/\\') * ')')^-1
|
|
|
|
+ l.Cg('glue', 'id') * l.Cg('(\\' * (
|
2021-07-03 22:38:32 +02:00
|
|
|
'line' * l.Cc(1)
|
|
|
|
+ 'baseline' * l.Cc(2)
|
|
|
|
+ 'par' * l.Cc(3)
|
|
|
|
+ 'abovedisplay' * l.Cc(4)
|
|
|
|
+ 'belowdisplay' * l.Cc(5)
|
|
|
|
+ 'abovedisplayshort' * l.Cc(6)
|
|
|
|
+ 'belowdisplayshort' * l.Cc(7)
|
|
|
|
+ 'left' * l.Cc(8)
|
|
|
|
+ 'right' * l.Cc(9)
|
|
|
|
+ 'top' * l.Cc(10)
|
|
|
|
+ 'splittop' * l.Cc(11)
|
|
|
|
+ 'tab' * l.Cc(12)
|
|
|
|
+ 'space' * l.Cc(13)
|
|
|
|
+ 'xspace' * l.Cc(14)
|
|
|
|
+ 'parfill' * l.Cc(15)
|
|
|
|
+ 'math' * l.Cc(16)
|
|
|
|
+ 'thinmu' * l.Cc(17)
|
|
|
|
+ 'medmu' * l.Cc(18)
|
|
|
|
+ 'thickmu' * l.Cc(19)) * 'skip)' + l.Cc(0), 'subtype')
|
|
|
|
* ' ' * l.Cg(scaled, 'width')
|
|
|
|
* (' plus ' * l.Cg(scaled, 'stretch') * l.Cg(glue_order, 'stretch_order') + l.Cg(l.Cc(0), 'stretch') * l.Cg(l.Cc(0), 'stretch_order'))
|
|
|
|
* (' minus ' * l.Cg(scaled, 'shrink') * l.Cg(glue_order, 'shrink_order') + l.Cg(l.Cc(0), 'shrink') * l.Cg(l.Cc(0), 'shrink_order'))
|
2021-07-04 03:18:36 +02:00
|
|
|
+ l.Cg('penalty', 'id') * ' ' * l.Cg(int, 'penalty')
|
|
|
|
+ l.Cg('mark', 'id') * l.Cg('s' * int + l.Cc(0), 'class') * l.Cg(balanced_braces, 'mark')
|
|
|
|
)) * -1
|
2021-07-03 22:38:32 +02:00
|
|
|
|
2021-07-04 03:18:36 +02:00
|
|
|
local simple_noad = l.Ct('\\' * (
|
|
|
|
'math' * l.Cg(
|
2021-05-28 18:25:25 +02:00
|
|
|
'ord' * l.Cc(0)
|
2021-06-01 22:29:48 +02:00
|
|
|
+ 'open' * l.Cc(6)
|
2021-05-29 12:39:24 +02:00
|
|
|
+ 'op\\limits' * l.Cc(2)
|
|
|
|
+ 'op\\nolimits' * l.Cc(3)
|
2021-05-28 18:25:25 +02:00
|
|
|
+ 'op' * l.Cc(1)
|
|
|
|
+ 'bin' * l.Cc(4)
|
|
|
|
+ 'rel' * l.Cc(5)
|
|
|
|
+ 'close' * l.Cc(7)
|
|
|
|
+ 'punct' * l.Cc(8)
|
|
|
|
+ 'inner' * l.Cc(9)
|
2021-05-29 12:39:24 +02:00
|
|
|
+ 'under' * l.Cc(10)
|
|
|
|
+ 'over' * l.Cc(11)
|
|
|
|
+ 'vcenter' * l.Cc(12)
|
2021-05-28 18:25:25 +02:00
|
|
|
, 'subtype') * l.Cg(l.Cc'noad', 'id')
|
2021-07-04 03:18:36 +02:00
|
|
|
+ l.Cg('radical', 'id') * l.Cg(delimiter_code, 'left') * l.Cg(l.Cc(0), 'subtype')
|
|
|
|
+ l.Cg('accent', 'id') * l.Cg(math_char, 'accent') * l.Cg(l.Cc(0), 'subtype')
|
|
|
|
+ l.Cg('left' * l.Cc(1)
|
|
|
|
+ 'middle' * l.Cc(2)
|
|
|
|
+ 'right' * l.Cc(3), 'subtype') * l.Cg(delimiter_code, 'delim')
|
2021-05-30 20:37:03 +02:00
|
|
|
* l.Cg(l.Cc(0), 'options') * l.Cg(l.Cc(0), 'height')
|
|
|
|
* l.Cg(l.Cc(0), 'depth') * l.Cg(l.Cc(0), 'height')
|
|
|
|
* l.Cg(l.Cc(-1), 'class') * l.Cg(l.Cc'fence', 'id')
|
2021-07-04 03:18:36 +02:00
|
|
|
+ l.Cg(
|
2021-05-29 13:34:38 +02:00
|
|
|
'display' * l.Cc(0)
|
|
|
|
+ 'text' * l.Cc(2)
|
|
|
|
+ 'scriptscript' * l.Cc(6)
|
|
|
|
+ 'script' * l.Cc(4), 'subtype') * l.Cg('style', 'id')
|
2021-07-04 03:18:36 +02:00
|
|
|
+ 'm' * l.Cg('kern', 'id') * l.Cg(scaled, 'kern') * 'mu' * l.Cg(l.Cc(99), 'subtype')
|
|
|
|
+ l.Cg('glue', 'id') * (
|
|
|
|
'(\\nonscript)' * l.Cg(l.Cc(98), 'subtype')
|
|
|
|
+ '(\\mskip)' * l.Cg(l.Cc(99), 'subtype')
|
|
|
|
* ' ' * l.Cg(scaled, 'width') * 'mu'
|
|
|
|
* (' plus ' * l.Cg(scaled, 'stretch') * l.Cg(glue_order_mu, 'stretch_order') + l.Cg(l.Cc(0), 'stretch') * l.Cg(l.Cc(0), 'stretch_order'))
|
|
|
|
* (' minus ' * l.Cg(scaled, 'shrink') * l.Cg(glue_order_mu, 'shrink_order') + l.Cg(l.Cc(0), 'shrink') * l.Cg(l.Cc(0), 'shrink_order'))
|
|
|
|
)
|
|
|
|
)) * -1
|
2021-07-03 22:38:32 +02:00
|
|
|
+ generic_simple_node
|
|
|
|
|
2021-07-04 03:18:36 +02:00
|
|
|
local simple_text = l.Ct('\\' * (
|
|
|
|
l.Cg('math', 'id') * l.Cg(
|
2021-07-03 22:38:32 +02:00
|
|
|
'on' * l.Cc(0)
|
2021-07-04 03:18:36 +02:00
|
|
|
+ 'off' * l.Cc(1)
|
|
|
|
, 'subtype') * l.Cg(', surrounded ' * scaled + l.Cc(0), 'surround')
|
|
|
|
)) * -1
|
2021-07-03 22:38:32 +02:00
|
|
|
+ generic_simple_node
|
2021-05-28 18:25:25 +02:00
|
|
|
|
2021-07-03 22:38:32 +02:00
|
|
|
local box_node = l.Ct('\\' * l.Cg('h' * l.Cc'hlist'
|
|
|
|
+ 'v' * l.Cc'vlist') * 'box'
|
|
|
|
* hdw
|
|
|
|
* (', glue set ' * l.Cg('- ' * l.Cc(2) + l.Cc(1), 'glue_sign')
|
|
|
|
* l.Cg(scaled/function (s) return s/65536 end, 'glue_set')
|
|
|
|
* l.Cg(glue_order, 'glue_order')
|
|
|
|
+ l.Cg(l.Cc(0), 'glue_sign') * l.Cg(l.Cc(0), 'glue_set') * l.Cg(l.Cc(0), 'glue_order'))
|
|
|
|
* l.Cg(', shifted ' * scaled + l.Cc(0), 'shift')) * -1
|
2021-06-27 04:30:49 +02:00
|
|
|
|
2021-05-29 12:39:24 +02:00
|
|
|
local fraction_noad = l.Ct('\\fraction, thickness '
|
|
|
|
* l.Cg('= default' * l.Cc(0x40000000) + scaled, 'width')
|
|
|
|
* l.Cg(', left-delimiter ' * delimiter_code, 'left')^-1 * l.Cg(', right-delimiter ' * delimiter_code, 'right')^-1
|
|
|
|
* l.Cg(l.Cc'fraction', 'id'))
|
2021-05-28 18:25:25 +02:00
|
|
|
* -1
|
|
|
|
|
2021-05-29 13:34:38 +02:00
|
|
|
local mathchoice_noad = l.Ct('\\mathchoice' * l.Cg(l.Cc'choice', 'id') * -1)
|
|
|
|
|
2021-06-27 05:39:42 +02:00
|
|
|
local mark_whatsit = '\\write' * ('-' + l.R'09'^1) * '{LUAMML_MARK_REF:' * (l.R'09'^1/tonumber) * ':'
|
2021-06-01 22:29:48 +02:00
|
|
|
|
2021-05-28 18:25:25 +02:00
|
|
|
local parse_list
|
2021-06-03 17:28:22 +02:00
|
|
|
local function parse_kernel(lines, i, prefix, parsed)
|
2021-05-28 18:25:25 +02:00
|
|
|
local line = lines[i]
|
|
|
|
if not line or line:sub(1, #prefix) ~= prefix then return nil, i end
|
|
|
|
local result = math_char:match(lines[i], #prefix + 1)
|
|
|
|
if result then return result, i+1 end
|
2021-06-27 04:30:49 +02:00
|
|
|
if box_node:match(lines[i], #prefix + 1) then return skip_list(lines, i+1, prefix .. '.') end
|
2021-06-03 17:28:22 +02:00
|
|
|
result, i = parse_list(lines, i, prefix, parsed)
|
2021-05-28 18:25:25 +02:00
|
|
|
return {list = result, id = 'sub_mlist'}, i
|
|
|
|
end
|
2021-06-27 04:30:49 +02:00
|
|
|
function skip_list(lines, i, prefix)
|
|
|
|
i = i or 1
|
|
|
|
local count = #lines
|
|
|
|
while i <= count and lines[i]:sub(1, #prefix) == prefix do
|
|
|
|
i = i + 1
|
|
|
|
end
|
|
|
|
return {id = 'sub_box', list = {}}, i
|
|
|
|
end
|
2021-06-03 17:28:22 +02:00
|
|
|
function parse_list(lines, i, prefix, parsed)
|
2021-05-28 18:25:25 +02:00
|
|
|
i = i or 1
|
|
|
|
prefix = prefix or ''
|
2021-05-29 12:39:24 +02:00
|
|
|
local head, last
|
2021-06-03 17:28:22 +02:00
|
|
|
local mark_environment = {data = parsed,}
|
2021-06-01 22:29:48 +02:00
|
|
|
local current_mark, current_count, current_offset
|
2021-05-28 18:25:25 +02:00
|
|
|
while true do
|
2021-06-01 22:29:48 +02:00
|
|
|
local skip
|
2021-05-28 18:25:25 +02:00
|
|
|
local line = lines[i]
|
|
|
|
if not line or line:sub(1, #prefix) ~= prefix then break end
|
|
|
|
local simple = simple_noad:match(line, #prefix+1)
|
|
|
|
if simple then
|
2021-06-03 17:28:22 +02:00
|
|
|
simple.nucleus, i = parse_kernel(lines, i + 1, prefix .. '.', parsed)
|
|
|
|
simple.sup, i = parse_kernel(lines, i, prefix .. '^', parsed)
|
|
|
|
simple.sub, i = parse_kernel(lines, i, prefix .. '_', parsed)
|
2021-05-29 12:39:24 +02:00
|
|
|
if last then
|
|
|
|
simple.prev, last.next = last, simple
|
|
|
|
end
|
|
|
|
last = simple
|
2021-05-28 18:25:25 +02:00
|
|
|
else
|
|
|
|
local fraction = fraction_noad:match(line, #prefix+1)
|
|
|
|
if fraction then
|
2021-06-03 17:28:22 +02:00
|
|
|
fraction.num, i = parse_kernel(lines, i + 1, prefix .. '\\', parsed)
|
|
|
|
fraction.denom, i = parse_kernel(lines, i, prefix .. '/', parsed)
|
2021-05-29 12:39:24 +02:00
|
|
|
if last then
|
|
|
|
fraction.prev, last.next = last, fraction
|
|
|
|
end
|
|
|
|
last = fraction
|
2021-05-28 18:25:25 +02:00
|
|
|
else
|
2021-05-29 13:34:38 +02:00
|
|
|
local mathchoice = mathchoice_noad:match(line, #prefix+1)
|
|
|
|
if mathchoice then
|
2021-06-03 17:28:22 +02:00
|
|
|
mathchoice.display, i = parse_list(lines, i + 1, prefix .. 'D', parsed)
|
|
|
|
mathchoice.text, i = parse_list(lines, i, prefix .. 'T', parsed)
|
|
|
|
mathchoice.script, i = parse_list(lines, i, prefix .. 'S', parsed)
|
|
|
|
mathchoice.scriptscript, i = parse_list(lines, i, prefix .. 's', parsed)
|
2021-05-29 13:34:38 +02:00
|
|
|
if last then
|
|
|
|
mathchoice.prev, last.next = last, mathchoice
|
|
|
|
end
|
|
|
|
last = mathchoice
|
|
|
|
else
|
2021-06-01 22:29:48 +02:00
|
|
|
skip = true
|
2021-06-03 17:28:22 +02:00
|
|
|
local mark = mark_whatsit:match(line, #prefix+1)
|
2021-06-01 22:29:48 +02:00
|
|
|
if mark then
|
2021-06-03 17:28:22 +02:00
|
|
|
local mark_table = assert(load('return {' .. assert(parsed.marks[mark], 'Undefined mark encountered') .. '}', nil, 't', mark_environment))()
|
2021-07-03 06:19:53 +02:00
|
|
|
if current_mark then
|
|
|
|
if (mark_table.count or 1) > current_count then
|
|
|
|
error'Invalid mark nesting'
|
|
|
|
end
|
|
|
|
-- Ignore new mark if existing mark is evaluated. This should be replaced with proper nesting
|
|
|
|
else
|
|
|
|
current_mark, current_count = mark_table, mark_table.count or 1
|
|
|
|
current_offset = mark_table.offset or current_count
|
|
|
|
end
|
2021-06-01 22:29:48 +02:00
|
|
|
i = i + 1
|
|
|
|
else
|
|
|
|
print(line, prefix, i)
|
|
|
|
print('unknown noad ' .. line:sub(#prefix+1))
|
|
|
|
i = i + 1
|
|
|
|
end
|
2021-05-29 13:34:38 +02:00
|
|
|
end
|
2021-05-28 18:25:25 +02:00
|
|
|
end
|
|
|
|
end
|
2021-05-29 12:39:24 +02:00
|
|
|
if not head then head = last end
|
2021-06-01 22:29:48 +02:00
|
|
|
if not skip and current_mark then
|
|
|
|
current_count = current_count - 1
|
|
|
|
current_offset = current_offset - 1
|
|
|
|
if current_offset == 0 then
|
|
|
|
properties[current_mark.nucleus and last.nucleus or last] = {mathml_core = current_mark.core}
|
|
|
|
else
|
|
|
|
properties[last] = {mathml_core = false}
|
|
|
|
end
|
|
|
|
if current_count == 0 then current_mark = nil end
|
|
|
|
end
|
2021-05-28 18:25:25 +02:00
|
|
|
end
|
2021-05-29 12:39:24 +02:00
|
|
|
return head, i
|
2021-05-28 18:25:25 +02:00
|
|
|
end
|
2021-05-29 12:39:24 +02:00
|
|
|
return parse_list
|