Compare commits

..

No commits in common. "v0.2.0" and "master" have entirely different histories.

44 changed files with 355 additions and 5852 deletions

View File

@ -1,38 +0,0 @@
name: CTAN upload
inputs:
filename:
required: true
dry-run:
required: true
uploader:
required: true
email:
required: true
version:
required: false
default: ${{ github.ref_name }}
runs:
using: composite
steps:
- name: Send to CTAN
uses: zauguin/ctan-upload@v0.1
with:
package-name: luaotfload
version: ${{ inputs.version }}
author: 'Marcel Krüger, LaTeX Project Team'
uploader: ${{ inputs.uploader }}
email: ${{ inputs.email }}
license: lppl1.3
summary: "Automatically generate MathML from LuaLaTeX math mode material"
ctan-path: /macros/luatex/latex/luamml
support: https://github.com/latex3/luamml/issues
update: true
topic: maths,luatex
description: |
LuaMML is an experimental package to automatically generate a MathML representation of mathematical expessions written in LuaLaTeX documents. These MathML representations can be used for improving accessibility or to ease conversion into new output formats like HTML.
filename: ${{ inputs.filename }}
dry-run: ${{ inputs.dry-run }}
# announcement-filename: ctan.ann
note: Uploaded automatically by GitHub Actions.

59
.github/tl_packages vendored
View File

@ -1,59 +0,0 @@
scheme-minimal latex-bin l3build
#Proudly generated by the Island of TeX's DEPendency Printer https://gitlab.com/islandoftex/texmf/depp
alphalph
amsfonts
amsmath
bigintcalc
bitset
bookmark
booktabs
cm
colortbl
csquotes
enumitem
etoolbox
fancyvrb
firstaid
fontspec
gettitlestring
graphics
graphics-cfg
graphics-def
hologo
hycolor
hypdoc
hyperref
iftex
infwarerr
intcalc
knuth-lib
kvdefinekeys
kvoptions
kvsetkeys
l3backend
l3build
l3kernel
l3packages
latex
latex-fonts
latex-lab
lm
lm-math
ltxcmds
luacolor
lualatex-math
pdfescape
pdfmanagement-testphase
pdftexcmds
psnfss
refcount
rerunfilecheck
stringenc
symbol
tagpdf
tools
underscore
unicode-math
uniquecounter
url
zapfding

View File

@ -1,78 +0,0 @@
name: Release
on:
# Only triggers for new tags
push:
tags: "*"
jobs:
# Mostly the same as the main.yaml workflow, but we only use a single job
l3build:
runs-on: ubuntu-latest
steps:
# Boilerplate
- name: Checkout repository
uses: actions/checkout@v4
- run: sudo apt-get install tidy
- name: Install TeX Live
uses: zauguin/install-texlive@v3
with:
# Here we use the same list of packages as in the testing workflow.
package_file: .github/tl_packages
- name: Run l3build
run: l3build ctan -H --show-log-on-error
- name: Upload package artifact
uses: actions/upload-artifact@v4
with:
name: Package
path: |
build/distrib/ctan/*.zip
ctan.ann
- name: Validate CTAN package
uses: ./.github/actions/ctan-upload
with:
uploader: Dummy Name
email: dryrun@example.com
filename: "build/distrib/ctan/luaotfload-ctan.zip"
dry-run: true
github:
runs-on: ubuntu-latex
needs:
- l3build
steps:
- name: Download package artifact
uses: actions/download-artifact@v4
with:
name: Package
- name: Create GitHub release
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5
id: release
with:
artifacts: "build/distrib/ctan/*.zip"
prerelease: ${{ endsWith(github.ref, '-dev') }}
token: ${{ secrets.GITHUB_TOKEN }}
bodyFile: ctan.ann
ctan-upload:
if: "${{ !endsWith(github.ref, '-dev') }}"
runs-on: ubuntu-latest
environment: CTAN
needs:
- l3build
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
sparse-checkout: .github
- name: Download package artifact
uses: actions/download-artifact@v4
with:
name: Package
- name: Upload CTAN package
uses: ./.github/actions/ctan-upload
with:
uploader: ${{ secrets.CTAN_NAME }}
email: ${{ secrets.CTAN_EMAIL }}
filename: "build/distrib/ctan/luaotfload-ctan.zip"
dry-run: false

View File

@ -1,53 +0,0 @@
name: Automated testing
# Currently we run in two situations:
on:
# Whenever someone pushes to a branch or tag in our repo
push:
branches:
- "*"
# Whenever a pull request is opened, reopened or gets new commits.
pull_request:
# This implies that for every push to a local branch in our repo for which a
# pull request is open this runs twice. But it's important to ensure that pull
# requests get tested even if their branch comes from a fork.
jobs:
l3build:
runs-on: ubuntu-latest
strategy:
matrix:
kind: [doc, test]
name: "${{ format('{0}', matrix.kind == 'doc' && 'Documentation' || 'Test suite') }}"
steps:
# Boilerplate
- name: Checkout repository
uses: actions/checkout@v4
- run: sudo apt-get install tidy
- name: Install TeX Live
uses: zauguin/install-texlive@v3
with:
# The list of packages to install is in a separate file under .github/tl_packages
# to allow reuse.
package_file: .github/tl_packages
cache_version: 0
- name: Run l3build
run: ${{ format('l3build {0} -q -H', matrix.kind == 'doc' && 'doc' || 'check --show-log-on-error') }}
# Now we create the artifacts: There are two cases where this happens.
# 1. If we failed running tests
- name: Archive failed test output
if: ${{ matrix.kind == 'test' && always() }}
uses: zauguin/l3build-failure-artifacts@v1
with:
name: testfiles-${{ matrix.platform }}
# Decide how long to keep the test output artifact:
retention-days: 3
# 2. If we succeed building documentation
- name: Archive documentation
if: ${{ matrix.kind == 'doc' && success() }}
uses: actions/upload-artifact@v4
with:
name: Documentation
path: "**/*.pdf"
# Decide how long to keep the test output artifact:
retention-days: 21

5
.gitignore vendored
View File

@ -1,5 +0,0 @@
/build/
*.aux
*.log
*.pdf
*.xml

View File

@ -1,14 +1,14 @@
# LuaMML: Automated LuaLaTeX math to MathML conversion
# Automated LuaLaTeX math to MathML conversion
**Highly experimental! At this point all interfaces may change without prior warning and many features aren't implemented yet. It is not ready for anything beyond simple experiments.**
This is an attempt to implement automatic conversion of LuaLaTeX inline and display math expressions into MathML code to aid with tagging.
It works best with `unicode-math`, but it can also be used with traditional math fonts if mappings to Unicode are provided.
## Installation
Run `l3build install` to install `luamml` into your local `texmf` tree.
## Usage
Add `\usepackage[tracing]{luamml-demo}` to print MathML to the terminal or `\usepackage[files]{luamml-demo}` to generate separate files with MathML output.
Alternatively it can be used with latex-lab to automatically integrate with tagging infrastucture.
## Demo
Run `lualatex test_tex` to see all equations from [our example file](./test_tex.tex) converted into MathML.
<!-- Also see a [`tagpdf` experiment using this to tag PDF formulas](https://github.com/u-fischer/tagpdf/blob/develop/experiments/exp-mathml-lua.tex). -->
<!-- If you are very brave you can also try running `pdflatex test_pdf` and afterwards run `./pdfmml.lua test_pdf.lua` to get pdflatex formulas converted. -->
To test it on your own files, add `\usepackage{luamml}` and `\tracingmathml=2` to your preamble.
Also see a [`tagpdf` experiment using this to tag PDF formulas](https://github.com/u-fischer/tagpdf/blob/develop/experiments/exp-mathml-lua.tex).

View File

@ -1,15 +1,7 @@
module = "luamml"
tdsroot = "lualatex"
tdsroot = "lualatex"
installfiles = { "luamml-*.lua", "*.sty" }
sourcefiles = { "luamml-*.lua", "*.sty", "*.dtx" }
typesetsuppfiles = { "*.tex" }
typesetsourcefiles = { "*.tex" }
stdengine = "luatex"
unpackfiles = { "*.dtx" }
typesetexe = "lualatex"
checkconfigs = {
'config-lua',
'config-pdf',
}
stdengine = "luatex"
checkengines = {"luatex"}
sourcefiles = installfiles

View File

@ -1,17 +0,0 @@
testfiledir = "testfiles-lua"
checkengines = {"luatex"}
stdengine = "luatex"
checkruns = 3
test_types = test_types or {}
test_types.mml = {
test = '.mlt',
generated = '.mml',
reference = '.mlr',
expectation = '.mle',
rewrite = function(source, result, engine, errlevels)
return os.execute(string.format('tidy -xml -indent -wrap -quiet --output-file "%s" "%s"', result, source))
end,
}
test_order = {'log', 'pdf', 'mml'}

View File

@ -1,33 +0,0 @@
testfiledir = "testfiles-pdf"
checkengines = {"pdftex"}
stdengine = "pdftex"
checkruns = 3
test_types = test_types or {}
test_types.tml = {
test = '.xrt',
generated = '.tml',
reference = '.txr',
expectation = '.xre',
rewrite = function(source, result, engine, errlevels)
local file = assert(io.open(source,"rb"))
local content = string.gsub(file:read("*all") .. "\n","\r\n","\n")
file:close()
local new_content = content
-- local new_content = processor(content,...)
local newfile = io.open(result,"w")
newfile:write(new_content)
newfile:close()
end,
}
test_types.mml = {
test = '.mlt',
generated = '.tml',
reference = '.mlr',
expectation = '.mle',
rewrite = function(source, result, engine, errlevels)
return os.execute(string.format('texlua pdfmml.lua "%s" | tidy -xml -indent -wrap -quiet --output-file "%s" -', source, result))
end,
}
test_order = {'log', 'pdf', 'tml', 'mml'}

View File

@ -4,28 +4,10 @@ local save_result = require'luamml-tex'.save_result
local store_column = require'luamml-table'.store_column
local store_tag = require'luamml-table'.store_tag
local get_table = require'luamml-table'.get_table
local set_row_attribute = require'luamml-table'.set_row_attribute
local to_text = require'luamml-lr'
local properties = node.get_properties_table()
local math_t = node.id'math'
local funcid = luatexbase.new_luafunction'__luamml_amsmath_add_last_to_row:'
token.set_lua('__luamml_amsmath_add_last_to_row:', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
-- TODO: Error handling etc
-- local box = token.scan_int()
local nest = tex.nest.top
local head, startmath = nest.head, nest.tail
repeat
startmath = startmath.prev
until startmath == head or (startmath.id == math_t and startmath.subtype == 0)
if startmath == head then return end
assert(startmath.id == node.id"math")
store_column(startmath)
end
local funcid = luatexbase.new_luafunction'__luamml_amsmath_add_box_to_row:'
token.set_lua('__luamml_amsmath_add_box_to_row:', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
@ -33,81 +15,24 @@ lua.get_functions_table()[funcid] = function()
-- local box = token.scan_int()
local boxnum = 0
local startmath = tex.box[boxnum].list
assert(startmath.id == math_t)
store_column(startmath)
assert(startmath.id == node.id"math")
store_column(startmath, true)
end
local funcid = luatexbase.new_luafunction'__luamml_amsmath_set_row_columnalign:n'
token.set_lua('__luamml_amsmath_set_row_columnalign:n', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
set_row_attribute('columnalign', token.scan_argument())
end
do
local saved
funcid = luatexbase.new_luafunction'__luamml_amsmath_save_inner_table:n'
token.set_lua('__luamml_amsmath_save_inner_table:n', funcid)
lua.get_functions_table()[funcid] = function()
-- TODO: Error handling etc
local kind = token.scan_argument()
local mml_table = get_table()
if not mml_table then return end
mml_table.displaystyle = true
local columns = node.count(node.id'align_record', tex.lists.align_head)//2
mml_table.columnalign = kind == 'gathered' and 'center' or string.rep('right left', columns, ' ')
local spacing = {}
for n in node.traverse_id(node.id'glue', tex.lists.align_head) do
spacing[#spacing+1] = n.width == 0 and '0' or string.format('%.3fpt', n.width/65781.76)
end
mml_table.columnspacing = #spacing > 3 and table.concat(spacing, ' ', 2, #spacing-2) or nil
saved = mml_table
end
funcid = luatexbase.new_luafunction'__luamml_amsmath_save_smallmatrix:'
token.set_lua('__luamml_amsmath_save_smallmatrix:', funcid)
lua.get_functions_table()[funcid] = function()
-- TODO: Error handling etc
local mml_table = get_table()
mml_table.align = 'axis'
mml_table.columnalign = 'center'
mml_table.columnspacing = '0.278em'
mml_table.rowspacing = string.format('%.3fpt', tex.lineskip.width/65781.76)
saved = {[0] = 'mpadded', width = '+0.333em', lspace = '0.167em', mml_table}
saved = mml_table
end
funcid = luatexbase.new_luafunction'__luamml_amsmath_finalize_inner_table:'
token.set_lua('__luamml_amsmath_finalize_inner_table:', funcid)
lua.get_functions_table()[funcid] = function()
-- TODO: Error handling etc
local vcenter = tex.nest.top.tail.nucleus
local props = properties[vcenter]
if not props then
props = {}
properties[vcenter] = props
end
props.mathml_table = assert(saved)
saved = nil
end
end
funcid = luatexbase.new_luafunction'__luamml_amsmath_finalize_table:n'
token.set_lua('__luamml_amsmath_finalize_table:n', funcid)
funcid = luatexbase.new_luafunction'__luamml_amsmath_finalize_table:'
token.set_lua('__luamml_amsmath_finalize_table:', funcid)
lua.get_functions_table()[funcid] = function()
-- TODO: Error handling etc
local kind = token.scan_argument()
local mml_table = get_table()
if not mml_table then return end
mml_table.displaystyle = true
local columns = node.count(node.id'align_record', tex.lists.align_head)//2
mml_table.columnalign = kind == 'align' and string.rep('right left', columns, ' ') or nil
mml_table.width = kind == 'multline' and '100%' or nil
-- mml_table.side = kind == 'multline' and 'rightoverlap' or nil
mml_table.columnalign = string.rep('right left', columns, ' ')
local spacing = {}
for n in node.traverse_id(node.id'glue', tex.lists.align_head) do
spacing[#spacing+1] = n.width == 0 and '0' or '.8em'
end
mml_table.columnspacing = #spacing > 3 and table.concat(spacing, ' ', 2, #spacing-2) or nil
mml_table.columnspacing = table.concat(spacing, ' ', 2, #spacing-2)
save_result(mml_table, true)
end

View File

@ -11,86 +11,7 @@ local radical_t, fraction_t, fence_t = node.id'radical', node.id'fraction', node
local math_char_t, sub_box_t, sub_mlist_t = node.id'math_char', node.id'sub_box', node.id'sub_mlist'
local function invert_table(t)
local t_inv = {}
for k, v in next, t do
t_inv[v] = k
end
return t_inv
end
local noad_names = node.subtypes'noad'
--[[ We could determine the noad subtypes dynamically:
local noad_sub = invert_table(noad_names)
local noad_ord = noad_sub.ord
local noad_op = noad_sub.opdisplaylimits
local noad_oplimits = noad_sub.oplimits
local noad_opnolimits = noad_sub.opnolimits
local noad_bin = noad_sub.bin
local noad_rel = noad_sub.rel
local noad_open = noad_sub.open
local noad_close = noad_sub.close
local noad_punct = noad_sub.punct
local noad_inner = noad_sub.inner
local noad_under = noad_sub.under
local noad_over = noad_sub.over
local noad_vcenter = noad_sub.vcenter
-- But the spacing table depends on their specific values anyway, so we just verify the values
]]
local noad_ord, noad_op, noad_oplimits, noad_opnolimits = 0, 1, 2, 3
local noad_bin, noad_rel, noad_open, noad_close, noad_punct = 4, 5, 6, 7, 8
local noad_inner, noad_under, noad_over, noad_vcenter = 9, 10, 11, 12
for i, n in ipairs{'ord', 'opdisplaylimits', 'oplimits', 'opnolimits', 'bin',
'rel', 'open', 'close', 'punct', 'inner', 'under', 'over', 'vcenter'} do
assert(noad_names[i-1] == n)
end
-- Attention, the spacing_table is indexed by subtype+1 since 1-based tables are faster in Lua
local spacing_table = {
{0 , '0.167em', '0.167em', '0.167em', '0.222em', '0.278em', 0 , 0 , 0 , '0.167em', 0 , 0 , 0 , },
{'0.167em', '0.167em', '0.167em', '0.167em', nil , '0.278em', 0 , 0 , 0 , '0.167em', '0.167em', '0.167em', '0.167em', },
nil,
nil,
{'0.222em', '0.222em', '0.222em', '0.222em', nil , nil , '0.222em', nil , nil , '0.222em', '0.222em', '0.222em', '0.222em', },
{'0.278em', '0.278em', '0.278em', '0.278em', nil , 0 , '0.278em', 0 , 0 , '0.278em', '0.278em', '0.278em', '0.278em', },
{0 , 0 , 0 , 0 , nil , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , },
{0 , '0.167em', '0.167em', '0.167em', '0.222em', '0.278em', 0 , 0 , 0 , '0.167em', 0 , 0 , 0 , },
{'0.167em', '0.167em', '0.167em', '0.167em', nil , '0.167em', '0.167em', '0.167em', '0.167em', '0.167em', '0.167em', '0.167em', '0.167em', },
{'0.167em', '0.167em', '0.167em', '0.167em', '0.222em', '0.278em', '0.167em', 0 , '0.167em', '0.167em', '0.167em', '0.167em', '0.167em', },
nil,
nil,
nil,
}
local spacing_table_script = {
{0 , '0.167em', '0.167em', '0.167em', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , },
{'0.167em', '0.167em', '0.167em', '0.167em', nil , 0 , 0 , 0 , 0 , 0 , '0.167em', '0.167em', '0.167em', },
nil,
nil,
{0 , 0 , 0 , 0 , nil , nil , 0 , nil , nil , 0 , 0 , 0 , 0 , },
{0 , 0 , 0 , 0 , nil , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , },
{0 , 0 , 0 , 0 , nil , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , },
{0 , '0.167em', '0.167em', '0.167em', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , },
{0 , 0 , 0 , 0 , nil , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , },
{0 , '0.167em', '0.167em', '0.167em', 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , },
nil,
nil,
nil,
}
do -- Fill the blanks
local st, sts = spacing_table, spacing_table_script
local st_op, sts_op = st[noad_op+1], sts[noad_op+1]
st[noad_oplimits+1], sts[noad_oplimits+1] = st_op, sts_op
st[noad_opnolimits+1], sts[noad_opnolimits+1] = st_op, sts_op
local st_ord, sts_ord = st[noad_ord+1], sts[noad_ord+1]
st[noad_under+1], sts[noad_under+1] = st_ord, sts_ord
st[noad_over+1], sts[noad_over+1] = st_ord, sts_ord
st[noad_vcenter+1], sts[noad_vcenter+1] = st_ord, sts_ord
end
local noad_sub = node.subtypes'noad'
local radical_sub = node.subtypes'radical'
local fence_sub = node.subtypes'fence'
@ -104,13 +25,8 @@ local digit_map = {["0"] = true, ["1"] = true,
["5"] = true, ["6"] = true, ["7"] = true,
["8"] = true, ["9"] = true,}
local always_mo = {["%"] = true, ["&"] = true, ["."] = true, ["/"] = true,
["\\"] = true, ["¬"] = true, [""] = true, [""] = true, [""] = true,
[""] = true, [""] = true, [""] = true, [""] = true, ["|"] = true,
[""] = true, [""] = true, [""] = true, [""] = true, [""] = true,}
-- Marker tables replacing the core operator for space like elements
local space_like = {}
-- Two marker tables. They are used instead of an embellished operator to mark space-like or user provided constructs
local user_provided, space_like = {}, {}
local nodes_to_table
@ -119,71 +35,47 @@ local function sup_style(s) return s//4*2+4+s%2 end
-- The _to_table functions generally return a second argument which is
-- could be (if it were a <mo>) a core operator of the embellishe operator
-- or space_like
-- or space_like/user_provided
-- acc_to_table is special since it's return value should
-- always be considered a core operator
-- We ignore large_... since they aren't used for modern fonts
local function delim_to_table(delim)
if not delim then return end
local props = properties[delim]
local mathml_core = props and props.mathml_core
local mathml_table = props and (props.mathml_table or mathml_core)
if mathml_table ~= nil then return mathml_table, mathml_core end
local mathml_filter = props and props.mathml_filter -- Kind of pointless since the arguments are literals, but present for consistency
local props = properties[delim] props = props and props.mathml_table
if props then return props end
local char = delim.small_char
if char == 0 then
local result = {[0] = 'mspace', width = string.format("%.3fpt", tex.nulldelimiterspace/65781.76)}
if mathml_filter then
return mathml_filter(result, space_like)
else
return result, space_like
end
return {[0] = 'mspace', width = string.format("%.3fpt", tex.nulldelimiterspace/65781.76)}, space_like
else
local fam = delim.small_fam
char = remap_lookup[fam << 21 | char]
local result = {[0] = 'mo', char, ['tex:family'] = fam ~= 0 and fam or nil, stretchy = not stretchy[char] or nil, lspace = 0, rspace = 0, [':nodes'] = {delim}, [':actual'] = char}
if mathml_filter then
return mathml_filter(result, result)
else
return result, result
end
local result = {[0] = 'mo', char, ['tex:family'] = fam ~= 0 and fam or nil, stretchy = not stretchy[char] or nil }
return result, result
end
end
-- Like kernel_to_table but always a math_char_t. Also creating a mo and potentially remapping to handle combining chars.
-- No lspace or space is set here since these never appear as core operators in an mrow.
-- Like kernel_to_table but always a math_char_t. Also creating a mo and potentially remapping to handle combining chars
local function acc_to_table(acc, cur_style, stretch)
if not acc then return end
local props = properties[acc]
local mathml_core = props and props.mathml_core
local mathml_table = props and (props.mathml_table or mathml_core)
if mathml_table ~= nil then return mathml_table, mathml_core end
local props = properties[acc] props = props and props.mathml_table
if props then return props end
if acc.id ~= math_char_t then
error'confusion'
end
local mathml_filter = props and props.mathml_filter -- Kind of pointless since the arguments are literals, but present for consistency
local fam = acc.fam
local char = remap_lookup[fam << 21 | acc.char]
char = remap_comb[char] or char
if stretch ~= not stretchy[char] then -- Handle nil gracefully in stretchy
stretch = nil
end
local result = {[0] = 'mo', char, ['tex:family'] = fam ~= 0 and fam or nil, stretchy = stretch, [':nodes'] = {acc}, [':actual'] = stretch and char or nil}
if mathml_filter then
return mathml_filter(result)
else
return result
end
return {[0] = 'mo', char, ['tex:family'] = fam ~= 0 and fam or nil, stretchy = stretch}
end
local function kernel_to_table(kernel, cur_style, text_families)
local function kernel_to_table(kernel, cur_style)
if not kernel then return end
local props = properties[kernel]
local mathml_core = props and props.mathml_core
local mathml_table = props and (props.mathml_table or mathml_core)
if mathml_table ~= nil then return mathml_table, mathml_core end
local mathml_filter = props and props.mathml_filter -- Kind of pointless since the arguments are literals, but present for consistency
local props = properties[kernel] props = props and props.mathml_table
if props then return props, user_provided end
local id = kernel.id
if id == math_char_t then
local fam = kernel.fam
@ -192,40 +84,27 @@ local function kernel_to_table(kernel, cur_style, text_families)
local result = {[0] = elem,
char,
['tex:family'] = fam ~= 0 and fam or nil,
mathvariant = utf8.len(char) == 1 and elem == 'mi' and utf8.codepoint(char) < 0x10000 and 'normal' or nil,
[':nodes'] = {kernel},
mathvariant = utf8.len(char) == 1 and elem == 'mi' and utf8.codepoint(char) < 0x10000 and 'normal' or nil
}
if mathml_filter then
return mathml_filter(result, result)
else
return result, result
end
return result, result
elseif id == sub_box_t then
local result
if kernel.list.id == hlist_t then -- We directly give up for vlists
result = to_text(kernel.list.head)
else
result = {[0] = 'mi', {[0] = 'mglyph', ['tex:box'] = kernel.list, [':nodes'] = {kernel}}}
end
if mathml_filter then
return mathml_filter(result, result)
local result = to_text(kernel.list.head)
return result, result
else
local result = {[0] = 'mi', {[0] = 'mglyph', ['tex:box'] = kernel.list}}
return result, result
end
elseif id == sub_mlist_t then
if mathml_filter then
return mathml_filter(nodes_to_table(kernel.list, cur_style, text_families))
else
return nodes_to_table(kernel.list, cur_style, text_families)
end
return nodes_to_table(kernel.list, cur_style)
else
error'confusion'
end
end
local function do_sub_sup(t, core, n, cur_style, text_families)
local sub = kernel_to_table(n.sub, sub_style(cur_style), text_families)
local sup = kernel_to_table(n.sup, sup_style(cur_style), text_families)
local function do_sub_sup(t, core, n, cur_style)
local sub = kernel_to_table(n.sub, sub_style(cur_style))
local sup = kernel_to_table(n.sup, sup_style(cur_style))
if sub then
if sup then
return {[0] = 'msubsup', t, sub, sup}, core
@ -246,7 +125,7 @@ local function maybe_to_mn(noad, core)
local after = noad.next
if not after then return end
if after.id ~= noad_t then return end
if after.subtype ~= noad_ord then return end
if noad_sub[after.subtype] ~= 'ord' then return end
after = after.nucleus
if not after then return end
if after.id ~= math_char_t then return end
@ -254,91 +133,70 @@ local function maybe_to_mn(noad, core)
core[0] = 'mn'
end
local function noad_to_table(noad, sub, cur_style, joining, bin_replacements, text_families)
local nucleus, core = kernel_to_table(noad.nucleus, sub == noad_over and cur_style//2*2+1 or cur_style, text_families)
if not nucleus then return end
if core and core[0] == 'mo' and core.minsize and not core.maxsize then
core.maxsize = core.minsize -- This happens when a half-specified delimiter appears alone in a list.
-- If it has a minimal size, it should be fixed to that size (since there is nothing bigger in it's list)
end
if sub == noad_ord and not (bin_replacements[node.direct.todirect(noad)] or (nucleus == core and #core == 1 and always_mo[core[1]])) then
local function noad_to_table(noad, sub, cur_style, mn)
local class = noad_sub[sub]
local nucleus, core = kernel_to_table(noad.nucleus, class == 'over' and cur_style//2*2+1 or cur_style)
if class == 'ord' then
if core and core[0] == 'mo' then
core[0] = 'mi'
core.stretchy, core.mathvariant = nil, #core == 1 and type(core[0]) == 'string' and utf8.len(core[0]) == 1 and utf8.codepoint(core[0]) < -0x10000 and 'normal' or nil
core['tex:class'] = nil
if not core.minsize and not core.movablelimits then
core[0] = 'mi'
core.movablelimits = nil
core.mathvariant = #core == 1 and type(core[1]) == 'string' and utf8.len(core[1]) == 1 and utf8.codepoint(core[1]) < 0x10000 and 'normal' or nil
core.stretchy, core.lspace, core.rspace = nil
end
end
if nucleus == core and #core == 1 then
if joining and joining[0] == 'mn' and core[0] == 'mi' and (core[1] == '.' or core[1] == ',') and maybe_to_mn(noad, core)
or core[0] == 'mn' or text_families[core['tex:family'] or 0] then
if joining and core[0] == joining[0] and core['tex:family'] == joining['tex:family'] then
joining[#joining+1] = core[1]
local cnodes = core[':nodes']
if cnodes then -- very likely
local jnodes = joining[':nodes']
if jnodes then -- very likely
table.move(cnodes, 1, #cnodes, #jnodes+1, jnodes)
else
joining[':nodes'] = cnodes
end
end
nucleus = do_sub_sup(joining, joining, noad, cur_style, text_families)
if nucleus == joining then
return nil, joining, joining
if mn and core[0] == 'mi' and (core[1] == '.' or core[1] == ',') and maybe_to_mn(noad, core) or core[0] == 'mn' then
if mn then
mn[#mn+1] = core[1]
nucleus = do_sub_sup(mn, mn, noad, cur_style)
if nucleus == mn then
return nil, mn, mn
else
return nucleus, joining, false
return nucleus, mn, false
end
elseif not noad.sub and not noad.sup then
return core, core, core
end
end
end
elseif sub == noad_op or sub == noad_oplimits or sub == noad_opnolimits or sub == noad_bin or sub == noad_rel or sub == noad_open
or sub == noad_close or sub == noad_punct or sub == noad_inner or sub == noad_ord then
elseif class == 'opdisplaylimits' or class == 'oplimits' or class == 'opnolimits' or class == 'bin' or class == 'rel' or class == 'open'
or class == 'close' or class == 'punct' or class == 'inner' then
if not core or not core[0] then
-- TODO
else
core[0] = 'mo'
if not core.minsize then
if stretchy[core[1]] then core.stretchy = false end
end
if stretchy[core[1]] then core.stretchy = false end
if core.mathvariant == 'normal' then core.mathvariant = nil end
core.lspace, core.rspace = 0, 0
end
nucleus['tex:class'] = noad_names[sub]
nucleus['tex:class'] = class
if (noad.sup or noad.sub) and (sub == noad_op or sub == noad_oplimits) then
if core and core[0] == 'mo' then core.movablelimits = sub == noad_op end
local sub = kernel_to_table(noad.sub, sub_style(cur_style), text_families)
local sup = kernel_to_table(noad.sup, sup_style(cur_style), text_families)
if (noad.sup or noad.sub) and (class == 'opdisplaylimits' or class == 'oplimits') then
nucleus.movablelimits = class == 'opdisplaylimits'
local sub = kernel_to_table(noad.sub, sub_style(cur_style))
local sup = kernel_to_table(noad.sup, sup_style(cur_style))
return {[0] = sup and (sub and 'munderover' or 'mover') or 'munder',
nucleus,
sub or sup,
sub and sup,
}, core
end
elseif sub == noad_under then
elseif class == 'under' then
return {[0] = 'munder',
nucleus,
{[0] = 'mo', '_',},
}, core
elseif sub == noad_over then
elseif class == 'over' then
return {[0] = 'mover',
nucleus,
{[0] = 'mo', '\u{203E}',},
}, core
elseif sub == noad_vcenter then -- Ignored. Nucleus will need special handling anyway
elseif class == 'vcenter' then -- Ignored. Nucleus will need special handling anyway
else
error[[confusion]]
end
return do_sub_sup(nucleus, core, noad, cur_style, text_families)
return do_sub_sup(nucleus, core, noad, cur_style)
end
local function accent_to_table(accent, sub, cur_style, text_families)
local nucleus, core = kernel_to_table(accent.nucleus, cur_style//2*2+1, text_families)
local function accent_to_table(accent, sub, cur_style)
local nucleus, core = kernel_to_table(accent.nucleus, cur_style//2*2+1)
local top_acc = acc_to_table(accent.accent, cur_style, sub & 1 == 1)
local bot_acc = acc_to_table(accent.bot_accent, cur_style, sub & 2 == 2)
return {[0] = top_acc and (bot_acc and 'munderover' or 'mover') or 'munder',
@ -360,17 +218,17 @@ style_table.crampedscript, style_table.crampedscriptscript =
style_table.display, style_table.text,
style_table.script, style_table.scriptscript
local function radical_to_table(radical, sub, cur_style, text_families)
local function radical_to_table(radical, sub, cur_style)
local kind = radical_sub[sub]
local nucleus, core = kernel_to_table(radical.nucleus, cur_style//2*2+1, text_families)
local nucleus, core = kernel_to_table(radical.nucleus, cur_style//2*2+1)
local left = delim_to_table(radical.left)
local elem
if kind == 'radical' or kind == 'uradical' then
-- FIXME: Check that this is really a square root
elem, core = {[0] = 'msqrt', nucleus, }, nil
elem, core = {[0] = 'msqrt', nucleus}, nil
elseif kind == 'uroot' then
-- FIXME: Check that this is really a root
elem, core = {[0] = 'msqrt', nucleus, kernel_to_table(radical.degree, 7, text_families)}, nil
elem, core = {[0] = 'msqrt', nucleus, kernel_to_table(radical.degree)}, nil
elseif kind == 'uunderdelimiter' then
elem, core = {[0] = 'munder', left, nucleus}, left
elseif kind == 'uoverdelimiter' then
@ -382,12 +240,12 @@ local function radical_to_table(radical, sub, cur_style, text_families)
else
error[[confusion]]
end
return do_sub_sup(elem, core, radical, cur_style, text_families)
return do_sub_sup(elem, core, radical, cur_style)
end
local function fraction_to_table(fraction, sub, cur_style, text_families)
local num, core = kernel_to_table(fraction.num, cur_style + 2 - cur_style//6*2, text_families)
local denom = kernel_to_table(fraction.denom, cur_style//2*2 + 3 - cur_style//6*2, text_families)
local function fraction_to_table(fraction, sub, cur_style)
local num, core = kernel_to_table(fraction.num, sup_style(cur_style))
local denom = kernel_to_table(fraction.denom, sub_style(cur_style))
local left = delim_to_table(fraction.left)
local right = delim_to_table(fraction.right)
local mfrac = {[0] = 'mfrac',
@ -414,31 +272,8 @@ end
local function fence_to_table(fence, sub, cur_style)
local delim, core = delim_to_table(fence.delim)
if core[0] ~= 'mo' then
return delim, core
end
core.fence, core.symmetric = 'true', 'true'
local options = fence.options
local axis
if fence.height ~= 0 or fence.depth ~= 0 then
axis = 0xA == options & 0xA
local exact = 0x18 == options & 0x18
-- We treat them always as exact. mpadded would allow us to support
-- non-exact ones too and I will implement that if I ever encounter
-- someone who does that intentionally. Until then, we warn people
-- since such fences are absurd.
if not exact then
texio.write_nl'luamml: The document uses a fence with \z
explicit dimensions but without the "exact" option. \z
This is probably a mistake.'
end
core.minsize = string.format("%.3fpt", (fence.height + fence.depth)/65781.76)
core.maxsize = core.minsize
else
axis = 0xC ~= options & 0xC
end
if not axis then
texio.write_nl'luamml: Baseline centered fence will be centered around math axis instead'
if delim[0] == 'mo' then
delim.fence = 'true'
end
return delim, core
end
@ -461,87 +296,67 @@ local function rule_to_table(rule, sub, cur_style)
if height == running_length then
height = '0.8em'
else
height = string.format("%.3fpt", height/65781.76)
height = height
end
local depth = rule.depth
if depth == running_length then
depth = '0.2em'
else
depth = string.format("%.3fpt", depth/65781.76)
depth = depth
end
return {[0] = 'mspace', mathbackground = 'currentColor', width = width, height = height, depth = depth}, space_like
end
-- The only part which changes the nodelist, we are converting bin into ord
-- nodes in the same way TeX would do it later anyway.
local function cleanup_mathbin(head)
local replacements = {}
local last = 'open' -- last sub if id was noad_t, left fence acts fakes being a open noad, bin are themselves. Every other noad is ord
for n, id, sub in node.traverse(head) do
if id == noad_t then
if sub == noad_bin then
if node.is_node(last) or last == noad_opdisplaylimits
or last == noad_oplimits or last == noad_opnolimits
or last == noad_rel or last == noad_open or last == noad_punct then
replacements[node.direct.todirect(n)] = true
n.subtype, last = noad_ord, noad_ord
else
last = n
end
else
if (sub == noad_rel or sub == noad_close or sub == noad_punct)
and node.is_node(last) then
replacements[node.direct.todirect(last)] = true
last.subtype = noad_ord
end
last = sub
end
elseif id == fence_t then
if sub == fence_sub.left then
last = noad_open
else
if node.is_node(last) then
replacements[node.direct.todirect(last)] = true
last.subtype = noad_ord, noad_ord
end
last = noad_ord
end
elseif id == fraction_t or id == radical_t or id == accent_t then
last = noad_ord
end
end
if node.is_node(last) then
replacements[node.direct.todirect(last)] = true
last.subtype = noad_ord
end
return replacements
end
function nodes_to_table(head, cur_style, text_families)
local bin_replacements = cleanup_mathbin(head)
function nodes_to_table(head, cur_style)
local t = {[0] = 'mrow'}
local result = t
local nonscript
local core, last_noad, last_core, joining = space_like, nil, nil, nil
local core, mn = space_like
local no_binop_context, last_noad, last_noad_core = true
for n, id, sub in node.traverse(head) do
local new_core, new_joining, new_node, new_noad
if last_noad and ((id == noad_t and noad_sub[sub] == 'punct') or (id == fence_t and fence_sub[sub] ~= 'left')) then
last_noad['tex:class'], last_noad_core[0] = nil, 'mi'
last_noad_core.stretchy, last_noad_core.mathvariant = nil, #last_noad_core == 1
and type(last_noad_core[0]) == 'string'
and utf8.len(last_noad_core[0]) == 1
and utf8.codepoint(last_noad_core[0]) < -0x10000
and 'normal' or nil
end
local new_core, new_mn
local props = properties[n]
local mathml_core = props and props.mathml_core
local mathml_table = props and (props.mathml_table or mathml_core)
if mathml_table ~= nil then
new_node, new_core = mathml_table, mathml_core
props = props and props.mathml_table
if props then
t[#t+1], new_core = props, user_provided
elseif id == noad_t then
local new_n
new_n, new_core, new_joining = noad_to_table(n, sub, cur_style, joining, bin_replacements, text_families)
if new_joining == false then
t[#t], new_joining = new_n, nil
else
new_node = new_n -- might be nil
local substr = noad_sub[sub]
if substr == 'bin' then
if no_binop_context then
sub, substr = 0, 'ord'
end
end
no_binop_context = false
or substr == 'bin'
or substr == 'opdisplaylimits'
or substr == 'oplimits'
or substr == 'opnolimits'
or substr == 'rel'
or substr == 'open'
or substr == 'punct'
local new_n
new_n, new_core, new_mn = noad_to_table(n, sub, cur_style, mn)
if new_mn == false then
t[#t], new_mn = new_n, nil
else
t[#t+1] = new_n -- might be nil
end
if substr == 'bin' then
last_noad, last_noad_core = new_n, new_core
else
last_noad, last_noad_core = nil, nil
end
new_noad = sub
elseif id == accent_t then
new_node, new_core = accent_to_table(n, sub, cur_style, text_families)
new_noad = noad_ord
t[#t+1], new_core = accent_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
elseif id == style_t then
if sub ~= cur_style then
if #t == 0 then
@ -561,77 +376,52 @@ function nodes_to_table(head, cur_style, text_families)
new_core = space_like
elseif id == choice_t then
local size = cur_style//2
new_node, new_core = nodes_to_table(n[size == 0 and 'display'
t[#t+1], new_core = nodes_to_table(n[size == 0 and 'display'
or size == 1 and 'text'
or size == 2 and 'script'
or size == 3 and 'scriptscript'
or assert(false)], 2*size, text_families), space_like
or assert(false)], 2*size), space_like
elseif id == radical_t then
new_node, new_core = radical_to_table(n, sub, cur_style, text_families)
new_noad = noad_ord
t[#t+1], new_core = radical_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
elseif id == fraction_t then
new_node, new_core = fraction_to_table(n, sub, cur_style, text_families)
new_noad = noad_inner
t[#t+1], new_core = fraction_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
elseif id == fence_t then
new_node, new_core = fence_to_table(n, sub, cur_style)
local class = n.class
new_noad = class >= 0 and class or sub == fence_sub.left and noad_open or noad_close
t[#t+1], new_core = fence_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
elseif id == kern_t then
if not nonscript then
new_node, new_core = space_to_table(n.kern, sub, cur_style)
t[#t+1], new_core = space_to_table(n.kern, sub, cur_style)
end
elseif id == glue_t then
if cur_style >= 4 or not nonscript then
if sub == 98 then -- TODO magic number
nonscript = true
else
new_node, new_core = space_to_table(n.width, sub, cur_style)
t[#t+1], new_core = space_to_table(n.width, sub, cur_style)
end
end
elseif id == rule_t then
new_node, new_core = rule_to_table(n, sub, cur_style)
t[#t+1], new_core = rule_to_table(n, sub, cur_style)
-- elseif id == disc_t then -- Uncommon, does not play nicely with math mode and no sensible mapping anyway
end -- The other possible ids are whatsit, penalty, adjust, ins, mark. Ignore them.
nonscript = nil
if core and new_core ~= space_like then
core = core == space_like and new_core or nil
end
if new_node then
if new_noad then
local space = last_noad and (cur_style >= 4 and spacing_table_script or spacing_table)[last_noad + 1][new_noad + 1] or 0
if assert(space) ~= 0 then
if new_core and new_core[0] == 'mo' then
new_core.lspace = space
elseif last_core and last_core[0] == 'mo' then
last_core.rspace = space
else
t[#t+1] = {[0] = 'mspace', width = space} -- TODO Move into operators whenever possible
end
end
last_noad, last_core = new_noad, new_core
elseif new_node[0] ~= 'mspace' or new_node.mathbackground then
last_core = nil
end
t[#t+1] = new_node
end
joining = new_joining
mn = new_mn
end
-- In TeX, groups are never space like, so we insert an artificial node instead.
-- This node should be ignored for most purposes
-- In TeX, groups are never space like
if core == space_like then
core = {[0] = 'mi', ['tex:ignore'] = 'true'}
core = {[0] = 'mi', intent = '@ignore'}
result[#result+1] = core
end
if t[0] == 'mrow' and #t == 1 then
assert(t == result)
result = t[1]
end
local mathml_filter = props and props.mathml_filter
if mathml_filter then
return mathml_filter(result, core)
else
return result, core
end
return result, core
end
local function register_remap(family, mapping)
@ -657,6 +447,6 @@ end
return {
register_family = register_remap,
process = function(head, style, families) return nodes_to_table(head, style or 2, families) end,
process = function(head, style) return nodes_to_table(head, style or 2) end,
make_root = to_math,
}

View File

@ -1,82 +0,0 @@
\NeedsTeXFormat{LaTeX2e}
\ProvidesExplPackage{luamml-demo}{2024-10-30}{0.2.0}{Reasonable default definitions for luamml}
\sys_if_engine_luatex:F {
\msg_new:nnn {luamml-demo} {pdftex-option-ignored} {Option~`#1'~is~being~ignored~in~pdfTeX~mode.}
\DeclareOption*{\msg_warning:nnx {luamml-demo} {pdftex-option-ignored} {\CurrentOption}}
\ProcessOptions\relax
\RequirePackage{luamml-pdf-demo}
\endinput
}
\RequirePackage{luamml}% Loading luamml is pretty much the point
\RequirePackage{amsmath,array}% These are more or less expected in luamml especially for advanced constructs
\AtBeginDocument{%
\@ifpackageloaded{unicode-math}{}{%
\RegisterFamilyMapping\symsymbols{oms}%
\RegisterFamilyMapping\symletters{oml}%
\RegisterFamilyMapping\symlargesymbols{omx}%
}
}
\bool_new:N \l__luamml_demo_structelem_bool
\DeclareOption{tracing}{
\tracingmathml=2
}
\DeclareOption{structelem}{
\bool_set_true:N \l__luamml_demo_structelem_bool
\luamml_structelem:
}
\DeclareOption{files}{
\int_new:N \g__luamml_demo_mathml_int
\luamml_set_filename:n {
\immediateassignment \int_gincr:N \g__luamml_demo_mathml_int
\jobname -formula- \int_use:N \g__luamml_demo_mathml_int .xml
}
}
\DeclareOption{l3build}{
\luamml_set_filename:n {
\jobname .mml
}
\luamml_begin_single_file:
}
\ProcessOptions\relax
\cs_new_eq:NN \LuaMMLSetFilename \luamml_set_filename:n
\cs_generate_variant:Nn \pdffile_filespec:nnn {ene}
\int_new:N \g__luamml_demo_af_int
\cs_new_protected:Npn \LuaMMLTagAF #1#2 {
\tag_mc_end_push:
\int_gincr:N \g__luamml_demo_af_int
\exp_args:Ne \pdf_object_new:nn{__luamml_demo_\int_use:N \g__luamml_demo_af_int}{dict}
\exp_args:Ne \tag_struct_begin:n{tag=Formula,AF=__luamml_demo_\int_use:N \g__luamml_demo_af_int,#1}
\bool_if:NF \l__luamml_demo_structelem_bool {
\tag_mc_begin:n{tag=Formula}
}
#2
\group_begin:
\pdfdict_put:nnn {l_pdffile/Filespec} {AFRelationship}{/Supplement}
\pdffile_filespec:ene
{ __luamml_demo_ \int_use:N \g__luamml_demo_af_int }
{ test.xml }
{ \luamml_get_last_mathml_stream:e{}\c_space_tl 0~R}
\group_end:
\bool_if:NF \l__luamml_demo_structelem_bool {
\tag_mc_end:
}
\tag_struct_end:
\tag_mc_begin_pop:n{}
}
\NewDocumentCommand\AnnotateFormula{ o m m }{%
\IfValueTF{#1}{%
\luamml_annotate:nen{#1}%
}{
\luamml_annotate:en
}{#2}{#3}
}
\cs_set_eq:NN \WriteoutFormula \luamml_pdf_write:

View File

@ -26,7 +26,6 @@ local remap_oml = { [0] =
0x1D465, 0x1D466, 0x1D467, 0x1D6A4, 0x1D6A5, 0x2118, 0x2192, nil,
}
-- Something fishy here. Starting with "3D the entries seem wrong
local remap_oms = { [0] =
0x2212, 0x22C5, 0xD7, 0x2A, 0xF7, 0x22C4, 0xB1, 0x2213,
0x2295, 0x2296, 0x2297, 0x2298, 0x2299, 0x25CB, 0x2218, 0x2219,
@ -35,7 +34,7 @@ local remap_oms = { [0] =
0x2190, 0x2192, 0x2191, 0x2193, 0x2194, 0x2197, 0x2198, 0x2243,
0x21D0, 0x21D2, 0x21D1, 0x21D3, 0x21D4, 0x2196, 0x2199, 0x221D,
0x2032, 0x221E, 0x2208, 0x220B, 0x25B3, 0x25BD, 0x0338, 0x21A6,
0x2200, 0x2203, 0xAC, 0x2205, 0x211C, 0x2111, 0x22A4, 0x22A5,
0x2200, 0x2203, 0xAC, 0x2205, 0x211C, 0x22A9, 0x22A4, 0x22A5,
0x2135, 0x1D49C, 0x212C, 0x1D49E, 0x1D49F, 0x2130, 0x2131, 0x1D4A2,
0x210B, 0x2110, 0x1D4A5, 0x1D4A6, 0x2112, 0x2133, 0x1D4A9, 0x1D4AA,
0x1D4AB, 0x1D4AC, 0x211B, 0x1D4AE, 0x1D4AF, 0x1D4B0, 0x1D4B1, 0x1D4B2,

View File

@ -1,7 +1,7 @@
local properties = node.get_properties_table()
local function to_unicode(head, tail)
local result, subresult, i = {[0] = 'mtext'}, {}, 0
local result, subresult, i = {[0] = 'mrow'}, {}, 0
local characters, last_fid
local iter, state, n = node.traverse(head)
while true do
@ -10,7 +10,7 @@ local function to_unicode(head, tail)
local props = properties[n]
if props and props.glyph_info then
i = i+1
result[i] = glyph_info
subresult[i] = glyph_info
else
local char, fid = node.is_glyph(n)
if char then
@ -23,23 +23,27 @@ local function to_unicode(head, tail)
i = i+1
if uni then
if type(uni) == 'number' then
result[i] = utf.char(uni)
subresult[i] = utf.char(uni)
else
result[i] = utf.char(table.unpack(uni))
subresult[i] = utf.char(table.unpack(uni))
end
else
if char < 0x110000 then
result[i] = utf.char(char)
subresult[i] = utf.char(char)
else
result[i] = '\u{FFFD}'
subresult[i] = '\u{FFFD}'
end
end
elseif node.id'math' == id then
if props then
local mml = props.saved_mathml_table or props.saved_mathml_core
local mml = props.saved_mathml_table
if mml then
i = i+1
result[i] = mml
if i ~= 0 then
result[#result+1] = {[0] = 'mtext', table.concat(subresult)}
for j = i, 1, -1 do subresult[j] = nil end
i = 0
end
result[#result+1] = mml
n = node.end_of_math(n)
end
end
@ -48,23 +52,46 @@ local function to_unicode(head, tail)
elseif node.id'glue' == id then
if n.width > 1000 then -- FIXME: Coordinate constant with tagpdf
i = i+1
result[i] = '\u{00A0}' -- non breaking space... There is no real reason why it has to be non breaking, except that MathML often ignore other spaces
subresult[i] = '\u{00A0}' -- non breaking space... There is no real reason why it has to be non breaking, except that MathML often ignore other spaces
end
elseif node.id'hlist' == id then
local nested = to_unicode(n.head)
table.move(nested, 1, #nested, i+1, result)
i = i+#nested
if nested[0] == 'mtext' and #nested == 1 and type(nested[1]) == 'string' then
i=i+1
subresult[i] = nested[1]
else
if i ~= 0 then
result[#result+1] = {[0] = 'mtext', table.concat(subresult)}
for j = i, 1, -1 do subresult[j] = nil end
i = 0
end
if nested[0] == 'mrow' then
table.move(nested, 1, #nested, #result+1, result)
else -- should be unreachable (propbably actually is reachable if the inner list only contains math
result[#result+1] = nested
end
end
elseif node.id'vlist' == id then
i = i+1
result[i] = '\u{FFFD}'
subresult[i] = '\u{FFFD}'
elseif node.id'rule' == id then
if n.width ~= 0 then
i = i+1
result[i] = '\u{FFFD}'
subresult[i] = '\u{FFFD}'
end
end -- CHECK: Everything else can probably be ignored, otherwise shout at me
end
end
if i ~= 0 then
result[#result+1] = {[0] = 'mtext', table.concat(subresult)}
end
if #result == 0 then
local r = {[0] = 'mtext', ''}
return r, r
elseif #result == 1 then
result = result[1]
if result[1] == 'mtext' then return result, result end
end
return result
end

View File

@ -1,172 +1,8 @@
\ProvidesExplPackage {luamml-patches-amsmath} {2024-10-30} {0.2.0}
\ProvidesExplPackage {luamml-patches-amsmath} {2021-04-23} {0.0.1-alpha}
{Feel free to add a description here}
\lua_now:n { require'luamml-amsmath' }
% For all of these changes, the redefinitions appear huge.
% But they are almost identical to the original and only
% add luamml commands in appropriate places, so they would
% mostly disappear if there were enough hooks in amsmath.
% aligned and friends
\cs_set:Npn \start@aligned #1#2 {
\RIfM@
\else
\nonmatherr@ { \begin { \@currenvir } }
\fi
\savecolumn@ % Assumption: called inside a group
\luamml_annotate:en {
core = false
} {
\alignedspace@left
}
\ams@start@box {#1} \bgroup
\maxfields@ #2 \relax
\ifnum \maxfields@ > \m@ne
\multiply \maxfields@ \tw@
\let \math@cr@@@ \math@cr@@@alignedat
\alignsep@ \z@skip
\else
\let \math@cr@@@ \math@cr@@@aligned
\alignsep@ \minalignsep
\fi
\Let@ \chardef \dspbrk@context \@ne
\default@tag
\spread@equation % no-op if already called
\global \column@ \z@
\ialign \bgroup
& \column@plus
\hfil
\strut@
$
\m@th
\displaystyle
{##}
\luamml_save:nNn {} \displaystyle {mtd}
$
\__luamml_amsmath_add_last_to_row:
\tabskip \z@skip
& \column@plus
$
\m@th
\displaystyle
{
{}
##
}
\luamml_save:nNn {} \displaystyle {mtd}
$
\__luamml_amsmath_add_last_to_row:
\hfil
\tabskip\alignsep@
\crcr
\ams@return@opt@arg
}
\renewcommand \gathered [1] [c] {
\RIfM@
\else
\nonmatherr@ { \begin {gathered} }
\fi
\luamml_annotate:en {
core = false
} {
\alignedspace@left
}
\ams@start@box {#1} \bgroup
\Let@
\chardef \dspbrk@context \@ne
\restore@math@cr
\spread@equation
\ialign \bgroup
\hfil
\strut@
$
\m@th
\displaystyle
##
\luamml_save:nNn {} \displaystyle {mtd}
$
\__luamml_amsmath_add_last_to_row:
\hfil
\crcr
\ams@return@opt@arg
}
\cs_set:Npn \endaligned {
\crcr
\__luamml_amsmath_save_inner_table:n \@currenvir
\egroup
\restorecolumn@
\egroup
\__luamml_amsmath_finalize_inner_table:
}
% gather
\cs_set:Npn \gather@ #1 {
\ingather@true
\let \split \insplit@
\let \tag \tag@in@align
\let \label \label@in@display
\chardef \dspbrk@context \z@
\intertext@ \displ@y@ \Let@
\let \math@cr@@@ \math@cr@@@gather
\gmeasure@ {#1}
\global \shifttag@false
\tabskip \z@skip
\global \row@ \@ne
\halign to \displaywidth \bgroup
\strut@
\setboxz@h {
$
\m@th
\displaystyle
{##}
\luamml_save:nNn {} \displaystyle {mtd}
$
}
\__luamml_amsmath_add_box_to_row:
\calc@shift@gather
\set@gather@field
\tabskip\@centering
&
\setboxz@h {
\strut@
{##}
}
\dim_compare:nNnF {0pt} = {
\box_wd:N \c_zero_int
} {
\__luamml_amsmath_set_tag:
}
\place@tag@gather
\tabskip \iftagsleft@
\gdisplaywidth@
\else
\z@skip
\span \fi
\crcr
#1
}
\cs_new_eq:NN \__luamml_amsmath_original_gmeasure:n \gmeasure@
\cs_set:Npn \gmeasure@ #1 {
\exp_last_unbraced:Nno
\use_ii_i:nn
{ \luamml_ignore: }
{ \__luamml_amsmath_original_gmeasure:n {#1} }
}
\cs_set:Npn \endgather {
\math@cr
\black@ \totwidth@
\__luamml_amsmath_finalize_table:n {gather}
\egroup
$$
\ignorespacesafterend
}
% align and friends
\cs_set:Npn \align@preamble {
&
\hfil
@ -175,12 +11,13 @@
\@lign
$
\m@th
\displaystyle
{##}
\displaystyle {
##
}
\ifmeasuring@
\luamml_ignore:
\luamml_flag_ignore:
\else
\luamml_save:nNn {} \displaystyle {mtd}
\luamml_flag_save:
\fi
$
}
@ -202,9 +39,9 @@
##
}
\ifmeasuring@
\luamml_ignore:
\luamml_flag_ignore:
\else
\luamml_save:nNn {} \displaystyle {mtd}
\luamml_flag_save:
\fi
$
}
@ -258,160 +95,15 @@
\cs_set:Npn \endalign {
\math@cr
\black@ \totwidth@
\__luamml_amsmath_finalize_table:n {align}
\__luamml_amsmath_finalize_table:
\egroup
\ifingather@
\restorealignstate@
\egroup
\nonumber
\ifnum0=`{\fi\iffalse}\fi
\ifnum0={\fi\iffalse}\fi
\else
$$
\fi
\ignorespacesafterend
}
% For a more interesting one, let's consider multline:
\cs_new_eq:NN \__luamml_amsmath_original_multline:n \multline@
\cs_set:Npn \multline@ #1 {
\__luamml_amsmath_original_multline:n {
\ifmeasuring@ \else
\__luamml_amsmath_set_row_columnalign:n {left}
\fi
#1
\ifmeasuring@ \else
\__luamml_amsmath_set_row_columnalign:n {right}
\fi
}
}
\cs_new_eq:NN \__luamml_amsmath_original_mmeasure:n \mmeasure@
\cs_set:Npn \mmeasure@ #1 {
\exp_last_unbraced:Nno
\use_ii_i:nn
{ \luamml_ignore: }
{ \__luamml_amsmath_original_mmeasure:n {#1} }
}
% Luckily, {multline} uses \endmultline@math in exactly
% the spot where we have to set the flag.
% Less luckily, \endmultline@math sometimes get overwritten for the last line.
% But that isn't a problem since we want special behavior there anyway.
\cs_set:Npn \endmultline@math {
\luamml_save:nNn {} \displaystyle {mtd}
$
\__luamml_amsmath_add_last_to_row:
}
\cs_set:Npn \rendmultline@ {
\iftag@
\luamml_save:nNn {} \displaystyle {mtd}
$
\__luamml_amsmath_add_last_to_row:
\let \endmultline@math \relax
\ifshifttag@
\hskip \multlinegap
\llap {
\vtop {
\raise@tag
\normalbaselines
\setbox \@ne \null
\dp \@ne \lineht@
\box \@ne
\hbox {
\strut@
\make@display@tag
}
}
}
\else
\hskip \multlinetaggap
\make@display@tag
\fi
\__luamml_amsmath_set_tag:
\else
\hskip \multlinegap
\fi
\hfilneg
\math@cr
\__luamml_amsmath_finalize_table:n {multline}
\egroup
$$
}
\cs_set:Npn \lendmultline@ {
\hfilneg
\hskip\multlinegap
\math@cr
\__luamml_amsmath_finalize_table:n {multline}
\egroup
$$
}
% Finally some slightly different stuff.
% While {matrix} is covered by {array}, we still have {smallmatrix}:
\renewenvironment {smallmatrix} {
\luamml_annotate:en {
core = false
} {
\null
\,
}
\vcenter \bgroup
\Let@
\restore@math@cr
\default@tag
\baselineskip 6 \ex@
\lineskip 1.5 \ex@
\lineskiplimit \lineskip
\ialign \bgroup
\hfil
$
\m@th
\scriptstyle
##
\luamml_save:nn {} {mtd} % No \scriptsize here since we want to add the mstyle nodes
$
\__luamml_amsmath_add_last_to_row:
\hfil
&&
\thickspace
\hfil
$
\m@th
\scriptstyle
##
\luamml_save:nn {} {mtd} % No \scriptsize here since we want to add the mstyle nodes
$
\__luamml_amsmath_add_last_to_row:
\hfil
\crcr
}{%
\crcr
\__luamml_amsmath_save_smallmatrix:
\egroup
\egroup
\__luamml_amsmath_finalize_inner_table:
\luamml_annotate:en {
core = false
} {
\,
}
}
% {cases} is defined by the kernel, but we patch the overwritten version by amsmath.
\cs_set:Npn \env@cases {
\let \@ifnextchar \new@ifnextchar
\left \lbrace
\def \arraystretch {1.2}
\array {@{}l@{\quad \luamml_ignore:}l@{}}
}
\cs_set:Npn \bBigg@ #1 #2 {
{
\ensuremath {
\Uvextensible height~#1 \big@size axis~exact~#2
}
}
}

View File

@ -1,37 +0,0 @@
\ProvidesExplPackage {luamml-patches-amstext} {2024-10-30} {0.2.0}
{Feel free to add a description here}
\int_new:N \g__luamml_amsmath_text_struct_int
\cs_set:Npn \textdef@ #1 #2 #3 {
\int_if_odd:nTF { \int_div_truncate:nn { \l__luamml_flag_int } { 8 } } {
\int_gincr:N \g__luamml_amsmath_text_struct_int
\tag_struct_begin:n {
tag = mtext/mathml,
stash,
label = __luamml_amsmath_text_ \int_use:N \g__luamml_amsmath_text_struct_int
}
\tag_mc_begin:n {
tag = mtext
}
\AnnotateFormula {
nucleus = true,
struct = "__luamml_amsmath_text_ \int_use:N \g__luamml_amsmath_text_struct_int"
}
} {
\use:n
}
{
\hbox {
{
\everymath {#1}
\let \f@size #2
\selectfont
#3
}
}
}
\int_if_odd:nT { \int_div_truncate:nn { \l__luamml_flag_int } { 8 } } {
\tag_mc_end:
\tag_struct_end:
}
}

View File

@ -1,4 +1,4 @@
\ProvidesExplPackage {luamml-patches-array} {2024-10-30} {0.2.0}
\ProvidesExplPackage {luamml-patches-array} {2021-04-23} {0.0.1-alpha}
{Feel free to add a description here}
\lua_now:n { require'luamml-array' }
@ -12,62 +12,47 @@
\hfil
\hskip 1sp
\d@llarbegin
\cs_if_eq:NNTF \d@llarbegin \begingroup {
\insert@column
\d@llarend
} {
\__luamml_array_init_col:
\insert@column
\luamml_save:nn {} {mtd}
\d@llarend
\__luamml_array_finalize_col:w 0~
}
\__luamml_array_init_col:
\insert@column
\luamml_flag_save:
\d@llarend
\__luamml_array_finalize_col:w 0~
\do@row@strut
\hfil
\or
\hskip 1sp
\d@llarbegin
\cs_if_eq:NNTF \d@llarbegin \begingroup {
\insert@column
\d@llarend
} {
\__luamml_array_init_col:
\insert@column
\luamml_save:nn {} {mtd}
\d@llarend
\__luamml_array_finalize_col:w 1~
}
\__luamml_array_init_col:
\insert@column
\luamml_flag_save:
\d@llarend
\__luamml_array_finalize_col:w 1~
\do@row@strut
\hfil
\or
\hfil
\hskip 1sp
\d@llarbegin
\cs_if_eq:NNTF \d@llarbegin \begingroup {
\insert@column
\d@llarend
} {
\__luamml_array_init_col:
\insert@column
\luamml_save:nn {} {mtd}
\d@llarend
\__luamml_array_finalize_col:w 2~
}
\__luamml_array_init_col:
\insert@column
\luamml_flag_save:
\d@llarend
\__luamml_array_finalize_col:w 2~
\do@row@strut
\or
\setbox \ar@mcellbox \vbox \@startpbox { \@nextchar }
\insert@pcolumn
\insert@column
\@endpbox
\ar@align@mcell
\do@row@strut
\or
\vtop \@startpbox { \@nextchar }
\insert@pcolumn
\insert@column
\@endpbox
\do@row@strut
\or
\vbox \@startpbox { \@nextchar }
\insert@pcolumn
\insert@column
\@endpbox
\do@row@strut
\fi
@ -75,27 +60,12 @@
\prepnext@tok
}
\IfPackageAtLeastTF {array} {2023/12/11} {
\cs_set:Npn \endarray {
\tbl_crcr:n{endarray}
\__luamml_array_save_array:
\egroup
\UseTaggingSocket{tbl/finalize}
\tbl_restore_outer_cell_data:
\egroup
\mode_if_math:T { \__luamml_array_finalize_array: }
\@arrayright
\gdef \@preamble {}
}
} {
\cs_new_eq:NN \insert@pcolumn \insert@column
\cs_set:Npn \endarray {
\crcr
\__luamml_array_save_array:
\egroup
\egroup
\mode_if_math:T { \__luamml_array_finalize_array: }
\@arrayright
\gdef \@preamble {}
}
\cs_set:Npn \endarray {
\crcr
\__luamml_array_save_array:
\egroup
\egroup
\__luamml_array_finalize_array:
\@arrayright
\gdef \@preamble {}
}

View File

@ -1,76 +0,0 @@
\ProvidesExplPackage {luamml-patches-kernel} {2024-10-30} {0.2.0}
{Feel free to add a description here}
\cs_set:Npn \mathsm@sh #1 #2 {
\setbox \z@ \hbox {
$
\m@th #1 {
#2
}
\luamml_save:nNn {mathsmash} #1 {mpadded}
\luamml_pdf_write:
$
}
\luamml_annotate:nen {2} {
nucleus = true,
core = consume_label('mathsmash', function(padded)
padded.height, padded.depth = 0, 0~
end),
} {
{}
\finsm@sh
}
}
\cs_set:Npn \mathph@nt #1 #2 {
\setbox \z@ = \hbox {
$
\m@th
#1
{#2}
\luamml_save:nNn {mathphant} #1 {mphantom}
$
}
\luamml_annotate:nen {1} {
nucleus = true,
core = {[0] = 'mpadded',
\ifh@\else
width = 0,
\fi
\ifv@\else
height = 0, depth = 0,
\fi
consume_label'mathphant',
}
} {
\finph@nt
}
}
\@ifpackageloaded {unicode-math} {} {
\cs_new:Npn \__luamml_kernel_define_character:Nnn #1#2#3 {
\cs_set:cpx { \cs_to_str:N #1 ~ } {
\luamml_annotate:nen {#2} {
nucleus = true, core = {[0] = 'mi', '\string\u{#3}'},
} {
\exp_not:v { \cs_to_str:N #1 ~ }
}
}
}
\__luamml_kernel_define_character:Nnn \models {3} {22a7}
\__luamml_kernel_define_character:Nnn \hookrightarrow {3} {21aa}
\__luamml_kernel_define_character:Nnn \hookleftarrow {3} {21a9}
\__luamml_kernel_define_character:Nnn \bowtie {3} {22c8}
\__luamml_kernel_define_character:Nnn \Longrightarrow {3} {27f9}
\__luamml_kernel_define_character:Nnn \longrightarrow {3} {27f6}
\__luamml_kernel_define_character:Nnn \Longleftarrow {3} {27f8}
\__luamml_kernel_define_character:Nnn \longleftarrow {3} {27f5}
\__luamml_kernel_define_character:Nnn \Longleftrightarrow {3} {27fa}
\__luamml_kernel_define_character:Nnn \longleftrightarrow {3} {27f7}
\__luamml_kernel_define_character:Nnn \longmapsto {4} {27fc}
}
\IfFileLoadedT {latex-lab-math.ltx} {
\RequirePackage{luamml-patches-lab-math}
}

View File

@ -1,20 +0,0 @@
\ProvidesExplPackage {luamml-patches-lab-math} {2024-10-30} {0.2.0}
{Feel free to add a description here}
\AddToHook{begindocument} {
\cs_set:Npn \common@align@ending {
\math@cr
\black@ \totwidth@
\__luamml_amsmath_finalize_table:n {align}
\egroup
\ifingather@
\restorealignstate@
\egroup
\nonumber
\ifnum0=`{\fi\iffalse}\fi
\else
$$
\fi
\ignorespacesafterend
}
}

View File

@ -1,35 +0,0 @@
\ProvidesExplPackage {luamml-patches-mathtools} {2024-10-26} {0.2.0}
{Feel free to add a description here}
\RequirePackage{luamml-patches-amsmath}
% see https://github.com/latex3/tagging-project/issues/734
\renewcommand*\MT_mult_internal:n [1]{
\MH_if_boolean:nF {outer_mult}{\alignedspace@left} %<-- requires amsmath 2016/11/05
\MT_next:
\bgroup
\Let@
\def\l_MT_multline_lastline_fint{0 }
\chardef\dspbrk@context\@ne \restore@math@cr
\MH_let:NwN \math@cr@@\MT_mult_mathcr_atat:w
\MH_let:NwN \shoveleft\MT_shoveleft:wn
\MH_let:NwN \shoveright\MT_shoveright:wn
\spread@equation
\MH_set_boolean_F:n {mult_firstline}
\MT_measure_mult:n {#1}
\MH_if_dim:w \l_MT_multwidth_dim<\l_MT_multline_measure_fdim
\MH_setlength:dn \l_MT_multwidth_dim{\l_MT_multline_measure_fdim}
\fi
\MH_set_boolean_T:n {mult_firstline}
\MH_if_num:w \l_MT_multline_lastline_fint=\@ne
\MH_let:NwN \math@cr@@ \MT_mult_firstandlast_mathcr:w
\MH_fi:
\ialign\bgroup
\hfil\strut@$\m@th\displaystyle{}##
\luamml_save:nNn {} \displaystyle {mtd}
$
\__luamml_amsmath_add_last_to_row:
\hfil
\crcr
\hfilneg
#1
}

View File

@ -1,50 +0,0 @@
\NeedsTeXFormat{LaTeX2e}
\ProvidesExplPackage{luamml-pdf-demo}{2024-10-30}{0.2.0}{Reasonable default definitions for luamml-pdf}
\RequirePackage{luamml-pdf}% Loading luamml-pdf is pretty much the point
% \RequirePackage{amsmath,array}% May come back if the patches get ported
% Delay family mappings to allow for replacements
\AddToHook{begindocument/before}{%
\@ifpackageloaded{unicode-math}{}{%
\RegisterFamilyMapping\symsymbols{oms}%
\RegisterFamilyMapping\symletters{oml}%
\RegisterFamilyMapping\symlargesymbols{omx}%
}
}
\cs_new_protected:Npn \LuaMMLSetFilename #1 {}
% TODO.
% \cs_generate_variant:Nn \pdffile_filespec:nnn {ene}
% \int_new:N \g__luamml_demo_af_int
% \cs_new_protected:Npn \LuaMMLTagAF #1#2 {
% \int_gincr:N \g__luamml_demo_af_int
% \exp_args:Ne \pdf_object_new:nn{__luamml_demo_\int_use:N \g__luamml_demo_af_int}{dict}
% \exp_args:Ne \tagstructbegin{tag=Formula,AF=__luamml_demo_\int_use:N \g__luamml_demo_af_int,#1}
% \bool_if:NF \l__luamml_demo_structelem_bool {
% \tagmcbegin{tag=Formula}
% }
% #2
% \group_begin:
% \pdfdict_put:nnn {l_pdffile/Filespec} {AFRelationship}{/Supplement}
% \pdffile_filespec:ene
% { __luamml_demo_ \int_use:N \g__luamml_demo_af_int }
% { test.xml }
% { \luamml_get_last_mathml_stream:e{}\c_space_tl 0~R}
% \group_end:
% \bool_if:NF \l__luamml_demo_structelem_bool {
% \tagmcend
% }
% \tagstructend
% }
\NewDocumentCommand\AnnotateFormula{ o m m }{%
\IfValueTF{#1}{%
\luamml_annotate:nen{#1}%
}{
\luamml_annotate:en
}{#2}{#3}
}
\cs_set_eq:NN \WriteoutFormula \luamml_pdf_write:

View File

@ -1,123 +0,0 @@
local struct_begin = token.create'tag_struct_begin:n'
local struct_use = token.create'tag_struct_use:n'
local struct_end = token.create'tag_struct_end:'
local mc_begin = token.create'tag_mc_begin:n'
local mc_end = token.create'tag_mc_end:'
local function escape_name(name)
return name
end
local function escape_string(str)
return str
end
local ltx
local function get_ltx()
if not ltx then
ltx = _ENV.ltx
if not ltx then
tex.error("LaTeX PDF support not loaded", {"Maybe try adding \\DocumentMetadata."})
ltx = {pdf = {object_id = function() return 0 end}}
end
end
return ltx
end
local mathml_ns_obj
local function get_mathml_ns_obj()
if not mathml_ns_obj then
mathml_ns_obj = get_ltx().pdf.object_id'tag/NS/mathml'
if not mathml_ns_obj then
tex.error("Failed to find MathML namespace", {"The PDF support does not know the mathml namespace"})
mathml_ns_obj = 0
end
end
return mathml_ns_obj
end
local attribute_counter = 0
local attributes = setmetatable({}, {__index = function(t, k)
attribute_counter = attribute_counter + 1
local attr_name = string.format('luamml_attr_%i', attribute_counter)
t[k] = attr_name
tex.runtoks(function()
tex.sprint(string.format('\\tagpdfsetup{newattribute={%s}{/O/NSO/NS %i 0 R',
attr_name, mathml_ns_obj or get_mathml_ns_obj()))
-- tex.sprint(string.format('\\tagpdfsetup{newattribute={%s}{/O/MathML-3',
-- attr_name))
tex.cprint(12, k)
tex.sprint'}}'
end)
return attr_name
end})
local mc_type = luatexbase.attributes.g__tag_mc_type_attr
local mc_cnt = luatexbase.attributes.g__tag_mc_cnt_attr
-- print('!!!', mc_type, mc_cnt)
local stash_cnt = 0
local attrs = {}
local function write_elem(tree, stash)
if tree[':struct'] then
return tex.runtoks(function()
return tex.sprint(struct_use, '{', tree[':struct'], '}')
end)
end
if not tree[0] then print('ERR', require'inspect'(tree)) end
local i = 0
for attr, val in next, tree do if type(attr) == 'string' and not string.find(attr, ':') and attr ~= 'xmlns' then
-- for attr, val in next, tree do if type(attr) == 'string' and string.byte(attr) ~= 0x3A then
i = i + 1
attrs[i] = string.format('/%s(%s)', escape_name(attr), escape_string(val))
end end
table.sort(attrs)
if stash then
stash_cnt = stash_cnt + 1
stash = '__luamml_stashed_' .. stash_cnt
tree[':struct'] = stash
stash = ', stash, label = ' .. stash
end
local attr_flag = i ~= 0 and ', attribute=' .. attributes[table.concat(attrs)]
tex.sprint(struct_begin, '{tag=' .. tree[0] .. '/mathml')
if stash then tex.sprint(stash) end
if attr_flag then tex.sprint(attr_flag) end
if tree[':actual'] then
tex.sprint(', actualtext = {')
tex.cprint(12, tree[':actual'])
tex.sprint'}'
end
tex.sprint'}'
for j = 1, i do attrs[j] = nil end
if tree[':nodes'] then
local n = tree[':nodes']
tex.runtoks(function()
tex.sprint{mc_begin, string.format('{tag=%s}', tree[0])}
-- NOTE: This will also flush all previous sprint's... That's often annoying, but in this case actually intentional.
end)
local mct, mcc = tex.attribute[mc_type], tex.attribute[mc_cnt]
for i = 1, #n do
node.set_attribute(n[i], mc_type, mct)
node.set_attribute(n[i], mc_cnt, mcc)
end
tex.runtoks(function()
tex.sprint(mc_end)
end)
end
for _, elem in ipairs(tree) do
if type(elem) ~= 'string' then
write_elem(elem)
end
end
tex.runtoks(function()
tex.sprint(struct_end)
end)
end
return function(element, stash)
return write_elem(element, stash)
end

View File

@ -4,23 +4,8 @@ local save_result = require'luamml-tex'.save_result
local properties = node.get_properties_table()
local glue_id = node.id'glue'
local tabskip_sub = 12
assert(node.subtypes'glue'[tabskip_sub] == 'tabskip')
local function store_get_row()
local row_temp
for i=tex.nest.ptr-1, 0, -1 do
local head = tex.nest[i].head
local glue = head.next
if glue and glue.id == glue_id and glue.subtype == tabskip_sub then
row_temp = head
break
end
end
if not row_temp then
error[[luamml_table's store function called outside of table]]
end
local row_temp = tex.nest[tex.nest.ptr-1].head
local props = properties[row_temp]
if not props then
props = {}
@ -35,39 +20,32 @@ local function store_get_row()
end
local function store_column_xml(mml, display)
if mml[0] ~= 'mtd' then
if display and mml[0] == 'mstyle' and mml.displaystyle == true then
mml[0], mml.displaystyle, mml.scriptlevel = 'mtd', nil, nil
else
if display and mml[0] ~= 'mstyle' then
mml = {[0] = 'mstyle', displaystyle = false, mml}
end
mml = {[0] = 'mtd', mml}
if display and mml[0] == 'mstyle' and mml.displaystyle == true then
mml[0], mml.displaystyle, mml.scriptlevel = 'mtd', nil, nil
else
if display and mml[0] ~= 'mstyle' then
mml = {[0] = 'mstyle', displaystyle = false, mml}
end
mml = {[0] = 'mtd', mml}
end
table.insert(store_get_row(), mml)
return mml
end
local function store_column(startmath)
local function store_column(startmath, display)
local props = properties[startmath]
if not props then return end
local mml = props.saved_mathml_table or props.saved_mathml_core
if mml then return store_column_xml(mml) end
local mml = props.saved_mathml_table
if mml then return store_column_xml(mml, display) end
end
local function store_tag(xml)
local mml_row = store_get_row()
xml.intent = ':equationlabel'
mml_row[0] = 'mlabeledtr'
table.insert(mml_row, 1, xml)
last_tag = nil
end
local function set_row_attribute(name, value)
local mml_row = store_get_row()
mml_row[name] = value
end
luatexbase.add_to_callback('hpack_filter', function(_, group)
if group ~= 'fin_row' then return true end
@ -105,6 +83,5 @@ return {
store_column = store_column,
store_column_xml = store_column_xml,
store_tag = store_tag,
set_row_attribute = set_row_attribute,
get_table = get_table,
}

View File

@ -1,128 +0,0 @@
local nest = tex.nest
local properties = node.get_properties_table()
local mark_environment = {
data = {
},
}
do
local _ENV = mark_environment
function consume_label(label, fn)
local mathml = data.mathml[label]
data.mathml[label] = nil
if fn then fn(mathml) end
return mathml
end
end
local function annotate()
local annotation, err = load( 'return {'
.. token.scan_argument()
.. '}', nil, 't', mark_environment)
if not annotation then
tex.error('Error while parsing LuaMML annotation', {err})
return 0
end
annotation = annotation()
local nesting = nest.top
local props = properties[nesting.head]
local current = props and props.luamml__annotate_context
if current then
current, props.luamml__annotate_context = current.head, current.prev
else
tex.error('Mismatched LuaMML annotation',
{'Something odd happened. Maybe you forgot braces around an annotated symbol in a subscript or superscript?'})
return 0
end
local after = nesting.tail
local count, offset = 0, annotation.offset
local marked
if current == after then
tex.error'Empty LuaMML annotation'
else
repeat
current = current.next
count = count + 1
if count == offset then
marked = current
elseif offset or current ~= after then
local props = properties[current]
if not props then
props = {}
properties[current] = props
end
props.mathml_table, props.mathml_core = nil, false
end
until current == after
if offset and not marked then
tex.error'Invalid offset in LuaMML annotation'
end
marked = marked or current
if annotation.nucleus then
marked = marked.nucleus
end
if marked then
local props = properties[marked]
if not props then
props = {}
properties[marked] = props
end
if annotation.core ~= nil then
props.mathml_core = annotation.core
end
if annotation.struct ~= nil then
local saved = props.mathml_filter
local struct = annotation.struct
function props.mathml_filter(mml, core)
mml[':struct'] = struct
if saved then
return saved(mml, core)
else
return mml, core
end
end
end
else
tex.error'Unable to annotate nucleus of node without nucleus'
end
end
return count
end
local funcid = luatexbase.new_luafunction'__luamml_annotate_begin:'
token.set_lua('__luamml_annotate_begin:', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
local top = nest.top
local temp = top.head
local props = properties[temp]
if not props then
props = {}
properties[temp] = props
end
props.luamml__annotate_context = {
prev = props.luamml__annotate_context,
head = top.tail,
}
end
funcid = luatexbase.new_luafunction'__luamml_annotate_end:we'
token.set_lua('__luamml_annotate_end:we', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
local count = token.scan_int()
local real_count = annotate()
if count ~= real_count then
tex.error('Incorrect count in LuaMML annotation', {
'A LuaMML annotation was discovered with an explicit count \z
which was not the same as the number of top-level nodes annotated.',
string.format('This can be fixed by changing the supplied count from %i to %i \z
or by omitting the count value entirely.', count, real_count)
})
end
end
funcid = luatexbase.new_luafunction'__luamml_annotate_end:e'
token.set_lua('__luamml_annotate_end:e', funcid, 'protected')
lua.get_functions_table()[funcid] = annotate
return mark_environment

View File

@ -5,43 +5,8 @@ local register_family = mlist_to_mml.register_family
local mappings = require'luamml-legacy-mappings'
local write_xml = require'luamml-xmlwriter'
local write_struct = require'luamml-structelemwriter'
local filename_token = token.create'l__luamml_filename_tl'
local label_token = token.create'l__luamml_label_tl'
local left_brace = token.new(string.byte'{', 1)
local right_brace = token.new(string.byte'}', 2)
local output_hook_token
local global_text_families = {}
local text_families_meta = {__index = function(t, fam)
if fam == nil then return nil end
local assignment = global_text_families[fam]
if assignment == nil then
local fid = node.family_font(fam)
local fontdir = font.getfont(fid)
if not fontdir then
-- FIXME(?): If there is no font...
error'Please load your fonts?!?'
end
assignment = not (fontdir.MathConstants and next(fontdir.MathConstants))
end
t[fam] = assignment
return assignment
end}
local properties = node.get_properties_table()
local mmode, hmode, vmode do
local result, input = {}, tex.getmodevalues()
for k,v in next, tex.getmodevalues() do
if v == 'math' then mmode = k
elseif v == 'horizontal' then hmode = k
elseif v == 'vertical' then vmode = k
else assert(v == 'unset')
end
end
assert(mmode and hmode and vmode)
end
local funcid = luatexbase.new_luafunction'RegisterFamilyMapping'
token.set_lua('RegisterFamilyMapping', funcid, 'protected')
@ -50,128 +15,70 @@ lua.get_functions_table()[funcid] = function()
local mapping = token.scan_string()
if mappings[mapping] then
register_family(fam, mappings[mapping])
if global_text_families[fam] == nil then
global_text_families[fam] = false
end
else
tex.error(string.format('Unknown font mapping %q', mapping))
end
end
local funcid = luatexbase.new_luafunction'RegisterFamilyMapping'
token.set_lua('RegisterTextFamily', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
local fam = token.scan_int()
local _kind = token.scan_string()
global_text_families[fam] = true
end
local function shallow_copy(t)
local tt = {}
for k,v in next, t do
tt[k] = v
end
return tt
end
-- Possible flag values:
-- 0: Skip
-- 1: Generate MathML, but only save it for later usage in startmath node
-- 3: Normal (This is the only supported one in display mode)
-- 11: Generate MathML structure elements
-- 0: Normal (This is the only supported one in display mode)
-- 1: Like 0, result is display math
-- 2: Generate MathML, but only save it for later usage in startmath node
-- 3: Skip
-- 4: Prepend node list from buffer before generating
-- 5: Like 5, result is display math
-- 6: 2+4
-- 7: Skip but save copy of node list in buffer
--
-- More generally, flags is a bitfield with the defined bits:
-- Bit 5-7: See Bit 4
-- Bit 4: Overwrite mathstyle with bit 9-11
-- Bit 3: Generate MathML structure elements
-- Bit 2: Change root element name for saved element
-- Bit 1: Save MathML as a fully converted formula
-- Bit 0: Save MathML for later usage in startmath node. Ignored for display math.
-- In other words:
-- Bit 1: Suppress output
-- Bit 0: Force display if 1 isn't set, if it is then skip MathML generation
-- Bit 2: Integrate with table mechanism
local out_file
local mlist_buffer
local mlist_result, mlist_display
local mlist_result
local undefined_cmd = token.command_id'undefined_cs'
local call_cmd = token.command_id'call'
local labelled_mathml = {}
local function save_result(xml, display, structelem)
mlist_result = make_root(xml, display and 0 or 2)
if out_file then
out_file:write(write_xml(mlist_result, tex.count.l__luamml_pretty_int & 1 == 1):sub(2) .. '\n')
else
token.put_next(filename_token)
local filename = token.scan_argument()
if filename ~= '' then
assert(io.open(filename, 'w'))
:write(write_xml(mlist_result, tex.count.l__luamml_pretty_int & 1 == 1):sub(2) .. '\n')
:close()
end
local function save_result(xml, display)
mlist_result, mlist_display = xml, display
if tex.count.tracingmathml > 1 then
-- Here xml gets wrapped in an mrow to avoid modifying it.
texio.write_nl(write_xml(make_root({[0] = 'mrow', xml}, display and 0 or 2)) .. '\n')
end
local tracing = tex.count.tracingmathml > 1
if tracing then
texio.write_nl(write_xml(mlist_result, tex.count.l__luamml_pretty_int & 2 == 2) .. '\n')
end
if output_hook_token then
tex.runtoks(function()
tex.sprint(-2, output_hook_token, left_brace, write_xml(mlist_result, tex.count.l__luamml_pretty_int & 4 == 4), right_brace)
end)
end
if tex.count.l__luamml_flag_int & 8 == 8 then
write_struct(mlist_result)
end
return mlist_result
end
luatexbase.add_to_callback('pre_mlist_to_hlist_filter', function(mlist, style)
if tex.nest.top.mode == mmode then -- This is a equation label generated with \eqno
return true
end
local flag = tex.count.l__luamml_flag_int
if flag & 3 == 0 then
if flag & 3 == 3 then
if flag & 4 == 4 then
assert(mlist_buffer == nil)
mlist_buffer = node.copy_list(mlist)
end
return true
end
local display = style == 'display'
local startmath = tex.nest.top.tail -- Must come before any write_struct calls which adds nodes
style = flag & 16 == 16 and flag>>5 & 0x7 or display and 0 or 2
local xml, core = process_mlist(mlist, style, setmetatable({}, text_families_meta))
if flag & 2 == 2 then
xml = save_result(shallow_copy(xml), display)
end
local new_mlist, buffer_tail
if flag & 4 == 4 then
local element_type = token.get_macro'l__luamml_root_tl'
if element_type ~= 'mrow' then
if xml[0] == 'mrow' then
xml[0] = element_type
else
xml = {[0] = element_type, xml}
end
end
new_mlist, buffer_tail = assert(mlist_buffer), node.tail(mlist_buffer)
mlist.prev, buffer_tail.next = buffer_tail, mlist
mlist_buffer = nil
else
new_mlist = mlist
end
if not display and flag & 1 == 1 then
local xml = process_mlist(new_mlist, style == 'display' and 0 or 2)
if flag & 2 == 0 then
save_result(xml, style == 'display' or flag & 1 == 1)
end
if style == 'text' then
local startmath = tex.nest.top.tail
local props = properties[startmath]
if not props then
props = {}
properties[startmath] = props
end
props.saved_mathml_table, props.saved_mathml_core = xml, core
token.put_next(label_token)
local label = token.scan_argument()
if label ~= '' then
if labelled_mathml[label] then
tex.error('MathML Label already in use', {
'A MathML expression has a label which is already used by another \z
formula. If you do not want to label this formula with a unique \z
label, set a empty label instead.'})
else
labelled_mathml[label] = xml
end
end
if flag & 10 == 8 then
write_struct(xml, true) -- This modifies xml in-place to reference the struture element
end
props.saved_mathml_table = xml
end
if buffer_tail then
mlist.prev, buffer_tail.next = nil, nil
node.flush_list(new_mlist)
end
return true
end, 'dump_list')
@ -184,7 +91,7 @@ lua.get_functions_table()[funcid] = function()
"I was asked to provide MathML code for the last formula, but there weren't any new formulas since you last asked."
})
end
local mml = write_xml(mlist_result, tex.count.l__luamml_pretty_int & 8 == 8)
local mml = write_xml(make_root(mlist_result, mlist_display and 0 or 2))
if tex.count.tracingmathml == 1 then
texio.write_nl(mml .. '\n')
end
@ -192,41 +99,6 @@ lua.get_functions_table()[funcid] = function()
mlist_result = nil
end
funcid = luatexbase.new_luafunction'luamml_begin_single_file:'
token.set_lua('luamml_begin_single_file:', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
token.put_next(filename_token)
local filename = token.scan_argument()
if filename ~= '' then
out_file = assert(io.open(filename, 'w'))
end
end
funcid = luatexbase.new_luafunction'luamml_end_single_file:'
token.set_lua('luamml_end_single_file:', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
if out_file then
out_file:close()
out_file = nil
end
end
funcid = luatexbase.new_luafunction'luamml_register_output_hook:N'
token.set_lua('__luamml_register_output_hook:N', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
output_hook_token = token.get_next()
end
funcid = luatexbase.new_luafunction'luamml_disable_output_hook:'
token.set_lua('__luamml_disable_output_hook:', funcid, 'protected')
lua.get_functions_table()[funcid] = function()
output_hook_token = nil
end
local annotate_context = require'luamml-tex-annotate'
annotate_context.data.mathml = labelled_mathml
return {
save_result = save_result,
labelled = labelled_mathml,
}

View File

@ -10,9 +10,7 @@ local escapes = {
['&'] = "&amp;",
}
local function escape_text(text)
return string.gsub(string.gsub(tostring(text), '["<>&]', escapes), '[\x00-\x08\x0B\x0C\x0E-\x1F]', function(x)
return string.format('^^%02x', string.byte(x))
end)
return string.gsub(tostring(text), '("<>&)', escapes)
end
local attrs = {}
@ -21,11 +19,8 @@ local function write_elem(tree, indent)
local escaped_name = escape_name(assert(tree[0]))
local i = 0
for attr, val in next, tree do if type(attr) == 'string' then
if not string.find(attr, ':', 1, true) then
-- if string.byte(attr) ~= 0x3A then
i = i + 1
attrs[i] = string.format(' %s="%s"', escape_name(attr), escape_text(val))
end
i = i + 1
attrs[i] = string.format(' %s="%s"', escape_name(attr), escape_text(val))
end end
table.sort(attrs)
local out = string.format('%s<%s%s', indent or '', escaped_name, table.concat(attrs))
@ -35,17 +30,14 @@ local function write_elem(tree, indent)
end
out = out .. '>'
local inner_indent = indent and indent .. ' '
local is_string
for _, elem in ipairs(tree) do
if type(elem) == 'string' then
if inner_indent and not is_string then
if inner_indent then
out = out .. inner_indent
end
out = out .. escape_text(elem)
is_string = true
else
out = out .. write_elem(elem, inner_indent)
is_string = nil
end
end
if indent then out = out .. indent end
@ -54,5 +46,5 @@ end
return function(element, indent, version)
return (version == '11' and '<?xml version="1.1"?>' or '') ..
write_elem(element, indent and '\n' or nil)
write_elem(element, indent and '' or nil)
end

View File

@ -1,552 +0,0 @@
% \iffalse meta-comment
%
%% Copyright (C) 2020-2024 by Marcel Krueger
%%
%% This file may be distributed and/or modified under the
%% conditions of the LaTeX Project Public License, either
%% version 1.3c of this license or (at your option) any later
%% version. The latest version of this license is in:
%%
%% http://www.latex-project.org/lppl.txt
%%
%% and version 1.3 or later is part of all distributions of
%% LaTeX version 2005/12/01 or later.
%
%<*batch>
%<*gobble>
\ifx\jobname\relax\let\documentclass\undefined\fi
\ifx\documentclass\undefined
\csname fi\endcsname
%</gobble>
\input docstrip.tex
\keepsilent
\generate{
\file{luamml.sty}{\from{luamml.dtx}{package,luatex}}
\file{luamml-pdf.sty}{\from{luamml.dtx}{package,pdftex}}
}
\endbatchfile
%</batch>
%<*gobble>
\fi
\expandafter\ifx\csname @currname\endcsname\empty
\csname fi\endcsname
%</gobble>
%<*driver>
\documentclass{l3doc}
\usepackage{luamml}
\usepackage{csquotes,luacolor}
\MakeShortVerb{\|}
\RecordChanges
\begin{document}
\tracingmathml2
\DocInput{luamml.dtx}
\PrintIndex
\PrintChanges
\end{document}
%</driver>
%<*gobble>
\fi
%</gobble>
% \fi
%
% \GetFileInfo{luamml.sty}
% \title{The \pkg{luamml} package%
% \thanks{This document corresponds to \pkg{luamml}~\fileversion, dated~\filedate.}%
% }
% \author{Marcel Krüger}
%
% \maketitle
%
% \begin{documentation}
% \section{Use case}
% When generating output for the web or tagged output, mathematical content should often be represented as MathML.
% This uses Lua\TeX~callbacks to automatically attempt to convert Lua\TeX~math mode output into MathML.
%
% \section{Usage}
% The \pkg{luamml} package is designed to be used in automated ways by other packages and usually should not be invoked directly by the end user.
% For experiments, \texttt{luamml-demo} is included which provides easier to use interfaces.
%
% Add in your preamble
% \begin{verbatim}
% \usepackage[files]{luamml-demo}
% \end{verbatim}
% This will trigger the output of individual files for each block of math output containing corresponding MathML.
%
% Alternatively
% \begin{verbatim}
% \usepackage[l3build]{luamml-demo}
% \end{verbatim}
% will generate a single file with a concatenation of all MathML blocks.
%
% For automated use, the \pkg{luamml} package can be included directly, followed by enclosing blocks which should generate files with \cmd{luamml_begin_single_file:} and \cmd{luamml_end_single_file:}.
% The filename can be set with \cmd{luamml_set_filename:n}.
%
% \section{Improving MathML conversion}
% When using constructs which do not automatically get converted in acceptable form, conversion hints can be provided with \cmd{luamml_annotate:en}.
% This allows to provide a replacement MathML structure in Lua table form, for example
% \begin{verbatim}
% \luamml_annotate:en {
% nucleus = true,
% core = {[0] = 'mi', 'TeX'},
% }{
% \hbox{\TeX}
% }
% \end{verbatim}
% produces a |<mi>TeX</mi>| element in the output instead of trying to import \TeX~as a mathematical expression.
% The table structure is explained in an appendix.
%
% \section{Features \& Limitations}
% Currently all mathematical expressions which purely contain Unicode encoded math mode material without embedded non-math should get converted successfully.
% Usage with non-Unicode math (\TeX's 8-bit math fonts) is highly experimental and undocumented.
% Any attempt to build complicated structures by embedding arbitrary \TeX\ code in the middle of math mode needs to have a MathML replacement specified.
% We try to automate more cases in the future.
%
% \appendix
% \input{luamml-algorithm}
% \end{documentation}
%
% \begin{implementation}
% \section{Package Implementation}
% \subsection{Initialization}
% \iffalse
%<*package>
% \fi
% \begin{macrocode}
%<@@=luamml>
%<*luatex>
\ProvidesExplPackage {luamml} {2024-10-30} {0.2.0}
{Automatically generate presentational MathML from LuaTeX math expressions}
%</luatex>
%<*pdftex>
\ProvidesExplPackage {luamml-pdf} {2024-10-30} {0.2.0}
{MathML generation for L̶u̶a̶pdfLaTeX}
%</pdftex>
% \end{macrocode}
%
% \subsection{Initialization}
% These variable have to appear before the Lua module is loaded and will be used to
% communicate information to the callback.
%
% Here \cs{tracingmathml} does not use a expl3 name since it is not intended for
% programming use but only as a debugging helper for the user.
% The other variables are internal, but we provide public interfaces for setting
% them later.
% \begin{macrocode}
\int_new:N \l__luamml_flag_int
\int_new:N \l__luamml_pretty_int
%<luatex>\tl_new:N \l__luamml_filename_tl
\tl_new:N \l__luamml_root_tl
\tl_set:Nn \l__luamml_root_tl { mrow }
\tl_new:N \l__luamml_label_tl
%<pdftex>\int_new:N \g__luamml_formula_id_int
%<luatex>\int_new:N \tracingmathml
\int_set:Nn \l__luamml_pretty_int { 1 }
% \end{macrocode}
%
% Now we can load the Lua module which defines the callback.
% Of course until pdf\TeX starts implementing \cs{directlua} this is only
% done in Lua\TeX.
% \begin{macrocode}
%<luatex>\lua_now:n { require'luamml-tex' }
% \end{macrocode}
%
% \subsection{Hook}
% We also call a hook with arguments at the end of every MathML conversion with the result.
% Currently only implemented in Lua\TeX{} since it immediately provides the output.
% \begin{macrocode}
%<*luatex>
\hook_new_with_args:nn { luamml / converted } { 1 }
\cs_new_protected:Npn \__luamml_output_hook:n {
\hook_use:nnw { luamml / converted } { 1 }
}
\__luamml_register_output_hook:N \__luamml_output_hook:n
%</luatex>
% \end{macrocode}
%
% \subsection{Flags}
% The most important interface is for setting the flag which controls how the
% formulas should be converted.
%
% \begin{macro}{\luamml_process:}
% Consider the current formula to be a complete, free-standing mathematical
% expression which should be converted to MathML. Additionally, the formula
% is also saved in the \texttt{start\_math} node as with
% \cs{luamml_save:}.
% \begin{macrocode}
\cs_new_protected:Npn \luamml_process: {
\tl_set:Nn \l__luamml_label_tl {}
\int_set:Nn \l__luamml_flag_int { 3 }
}
% \end{macrocode}
% Temporarly for compatibility
% \begin{macrocode}
\cs_set_eq:NN \luamml_flag_process: \luamml_process:
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\__luamml_maybe_structelem:}
% A internal helper which can be added to a tag to preserve the external state
% of the structelem flag.
% \begin{macrocode}
\cs_new:Npn \__luamml_maybe_structelem: {
(
8 * \int_mod:nn {
\int_div_truncate:nn { \l__luamml_flag_int } {8}
} {2}
) +
}
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\__luamml_style_to_num:N}
% \begin{macrocode}
\cs_new:Npn \__luamml_style_to_num:N #1 {
%<luatex> 32 * #1
%<*pdftex>
\token_case_meaning:NnF #1 {
\displaystyle {0}
\textstyle {32}
\scriptstyle {64}
\scriptscriptstyle {96}
} {
\Invalid_mathstyle
}
%</pdftex>
}
% \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{\luamml_save:n,
% \luamml_save:nN,
% \luamml_save:nn,
% \luamml_save:nNn}
% Convert the current formula but only save it's representation in the math
% node without emitting it as a complete formula. This is useful when the
% expression forms part of a bigger formula and will be integrated into it's
% MathML tables later by special code.
% It optionally accepts three parameters: A label, one math style command
% (\cs{displaystyle}, \cs{textstyle}, etc.) which is the implicit math style
% (so the style which the surrounding code expects this style to have) and a
% name for the root element (defaults to \texttt{mrow}).
% If the root element name is \texttt{mrow}, it will get suppressed in some
% cases.
% \begin{macrocode}
\cs_new_protected:Npn \luamml_save:n #1 {
\tl_set:Nn \l__luamml_label_tl {#1}
\int_set:Nn \l__luamml_flag_int { \__luamml_maybe_structelem: 1 }
}
\cs_new_protected:Npn \luamml_save:nN #1#2 {
\tl_set:Nn \l__luamml_label_tl {#1}
\int_set:Nn \l__luamml_flag_int { \__luamml_maybe_structelem: 17 + \__luamml_style_to_num:N #2 }
}
\cs_new_protected:Npn \luamml_save:nn #1 {
\tl_set:Nn \l__luamml_label_tl {#1}
\int_set:Nn \l__luamml_flag_int { \__luamml_maybe_structelem: 5 }
\tl_set:Nn \l__luamml_root_tl
}
\cs_new_protected:Npn \luamml_save:nNn #1#2 {
\tl_set:Nn \l__luamml_label_tl {#1}
\int_set:Nn \l__luamml_flag_int { \__luamml_maybe_structelem: 21 + \__luamml_style_to_num:N #2 }
\tl_set:Nn \l__luamml_root_tl
}
% \end{macrocode}
% Temporarly for compatibility
% \begin{macrocode}
\cs_set_eq:NN \luamml_flag_save:n \luamml_save:n
\cs_set_eq:NN \luamml_flag_save:nN \luamml_save:nN
\cs_set_eq:NN \luamml_flag_save:nn \luamml_save:nn
\cs_set_eq:NN \luamml_flag_save:nNn \luamml_save:nNn
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\luamml_ignore:}
% Completely ignore the math mode material.
% \begin{macrocode}
\cs_new_protected:Npn \luamml_ignore: {
\int_set:Nn \l__luamml_flag_int { 0 }
}
% \end{macrocode}
% Temporarly for compatibility
% \begin{macrocode}
\cs_set_eq:NN \luamml_flag_ignore: \luamml_ignore:
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\luamml_structelem:}
% Like \cs{luamml_process:}, but additionally adds PDF structure
% elements. This only works in Lua\TeX\ and requires that the \pkg{tagpdf} package
% has been loaded \emph{before} \texttt{luamml}.
% \begin{macrocode}
%<*luatex>
\cs_new_protected:Npn \luamml_structelem: {
\tl_set:Nn \l__luamml_label_tl {}
\int_set:Nn \l__luamml_flag_int { 11 }
}
% \end{macrocode}
% Temporarly for compatibility
% \begin{macrocode}
\cs_set_eq:NN \luamml_flag_structelem: \luamml_structelem:
%</luatex>
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\luamml_set_filename:n}
% Allows to set a filename to which the generated MathML gets written.
% Previous content from the file will get overwritten. This includes results
% written by a previous formula. Therefore this has to be called separately
% for every formula or it must expand to different values to be useful.
% The value is fully expanded when the file is written.
%
% Only complete formulas get written into files (so formulas where
% \cs{luamml_process:} or \cs{luamml_structelem:} are in effect).
%
% Only implemented in Lua\TeX, in pdf\TeX\ the arguments for \texttt{pdfmml}
% determine the output location.
% \begin{macrocode}
%<*luatex>
\cs_new_protected:Npn \luamml_set_filename:n {
\tl_set:Nn \l__luamml_filename_tl
}
%</luatex>
% \end{macrocode}
% \end{macro}
%
% \begin{macro}{\luamml_begin_single_file:, \luamml_end_single_file:}
% Everything between these two commands gets written into the same XML file.
% The filename is expanded when \cs{luamml_begin_single_file:} gets executed.
%
% (Implemented in Lua)
% \end{macro}
%
% By default, the flag is set to assume complete formulas.
% \begin{macrocode}
\luamml_process:
% \end{macrocode}
%
% \subsection{Annotations}
% These are implemented very differently depending on the engine, but the interface
% should be the same.
% \subsubsection{Lua\TeX}
% \begin{macrocode}
%<*luatex>
% \end{macrocode}
% \begin{macro}{\luamml_annotate:nen, \luamml_annotate:en}
% A simple annotation scheme: The first argument is the number of top level
% noads to be annotated, the second parameter the annotation and the third
% parameter the actual list of math tokens. The first argument can be omitted to
% let Lua\TeX determine the number itself.
%
% Passing the first parameter explicitly is useful for any annotations which
% should be compatible with future pdf\TeX versions of this functionality.
% \begin{macrocode}
\cs_new_protected:Npn \luamml_annotate:nen #1#2#3 {
\__luamml_annotate_begin:
#3
\__luamml_annotate_end:we \tex_numexpr:D #1 \scan_stop: {#2}
}
\cs_new_protected:Npn \luamml_annotate:en #1#2 {
\__luamml_annotate_begin:
#2
\__luamml_annotate_end:e {#1}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%</luatex>
% \end{macrocode}
% \subsubsection{pdf\TeX}
% \begin{macrocode}
%<*pdftex>
% \end{macrocode}
% \begin{macro}{\__luamml_pdf_showlists:}
% Here and in many other locations the \pdfTeX{} implementation is based on \cs{showlists},
% so we define a internal wrapper which sets all relevant parameters.
% \begin{macrocode}
\cs_if_exist:NTF \showstream {
\iow_new:N \l__luamml_pdf_stream
\iow_open:Nn \l__luamml_pdf_stream { \jobname .tml }
\cs_new_protected:Npn \__luamml_pdf_showlists: {
\group_begin:
\int_set:Nn \tex_showboxdepth:D { \c_max_int }
\int_set:Nn \tex_showboxbreadth:D { \c_max_int }
\showstream = \l__luamml_pdf_stream
\tex_showlists:D
\group_end:
}
} {
\cs_set_eq:NN \l__luamml_pdf_stream \c_log_iow
\cs_set_eq:NN \__luamml_pdf_set_showstream: \scan_stop:
\cs_new_protected:Npn \__luamml_pdf_showlists: {
\group_begin:
\int_set:Nn \l_tmpa_int { \tex_interactionmode:D }
\int_set:Nn \tex_interactionmode:D { 0 }
\int_set:Nn \tex_showboxdepth:D { \c_max_int }
\int_set:Nn \tex_showboxbreadth:D { \c_max_int }
\tex_showlists:D
\int_set:Nn \tex_interactionmode:D { \l_tmpa_int }
\group_end:
}
}
% \end{macrocode}
% \end{macro}
%
%
% \begin{macro}{\luamml_annotate:nen, \luamml_annotate:en}
% Now we can define the annotation commands for pdf\TeX.
% \begin{macrocode}
\cs_generate_variant:Nn \tl_to_str:n { e }
\int_new:N \g__luamml_annotation_id_int
\cs_new_protected:Npn \luamml_annotate:nen #1#2#3 {
\int_gincr:N \g__luamml_annotation_id_int
\iow_shipout_x:Nx \l__luamml_pdf_stream {
LUAMML_MARK_REF:
\int_use:N \g__luamml_annotation_id_int
:
}
\iow_now:Nx \l__luamml_pdf_stream {
LUAMML_MARK:
\int_use:N \g__luamml_annotation_id_int
:
count = \int_eval:n {#1},
#2
\iow_newline:
LUAMML_MARK_END
}
#3
}
\cs_new_protected:Npn \luamml_annotate:en #1#2 {
\int_gincr:N \g__luamml_annotation_id_int
\iow_shipout_x:Nx \l__luamml_pdf_stream {
LUAMML_MARK_REF:
\int_use:N \g__luamml_annotation_id_int
:
}
\iow_now:Nx \l__luamml_pdf_stream {
LUAMML_MARK:
\int_use:N \g__luamml_annotation_id_int
:
count = data.count[\int_use:N \g__luamml_annotation_id_int],
#1
\iow_newline:
LUAMML_MARK_END
}
\use:x {
\iow_now:Nn \l__luamml_pdf_stream {
LUAMML_COUNT:
\int_use:N \g__luamml_annotation_id_int
}
\__luamml_pdf_showlists:
\exp_not:n {#2}
\iow_now:Nn \l__luamml_pdf_stream {
LUAMML_COUNT_END:
\int_use:N \g__luamml_annotation_id_int
}
\__luamml_pdf_showlists:
}
}
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
%</pdftex>
% \end{macrocode}
%
% \subsection{Trigger for specific formula}
% This only applies for pdf\TeX\ since in Lua\TeX\ everything is controlled by the callback,
% but for compatibility the function is defined anyway.
%
% \begin{macro}{\luamml_pdf_write:}
% We could accept parameters for the flag and tag here, but for compatibility
% with Lua\TeX they are passed in macros instead.
% \begin{macrocode}
%<*pdftex>
\cs_new_protected:Npn \luamml_pdf_write: {
\int_gincr:N \g__luamml_formula_id_int
\iow_now:Nx \l__luamml_pdf_stream {
LUAMML_FORMULA_BEGIN:
\int_use:N \g__luamml_formula_id_int
:
\int_use:N \l__luamml_flag_int
:
\l__luamml_root_tl
:
\l__luamml_label_tl
}
\__luamml_pdf_showlists:
\iow_now:Nx \l__luamml_pdf_stream {
LUAMML_FORMULA_END
}
}
%</pdftex>
%<luatex>\cs_new_eq:NN \luamml_pdf_write: \scan_stop:
% \end{macrocode}
% \end{macro}
%
% \begin{macrocode}
% \end{macrocode}
%
% \subsection{Further helpers}
%
% \begin{macro}{\RegisterFamilyMapping}
% The Lua version of this is defined in the Lua module.
% \begin{macrocode}
%<*pdftex>
\NewDocumentCommand \RegisterFamilyMapping {m m} {
\iow_now:Nx \l__luamml_pdf_stream {
LUAMML_INSTRUCTION:REGISTER_MAPPING: \int_use:N #1 : #2
}
}
%</pdftex>
% \end{macrocode}
% \end{macro}
%
%
% \subsection{Patching}
% For some packages, we ship with patches to make them more compatible and to
% demonstrate how other code can be patched to work with \texttt{luamml}.
%
% These are either loaded directly if the packages are loaded or delayed using
% \LaTeX's hook system otherwise.
% \begin{macro}{\__luamml_patch_package:nn, \__luamml_patch_package:n}
% For this, we use two helpers: First a wrapper which runs arbitrary code either
% now (if the package is already loaded) or as soon as the package loads, second
% an application of the first one to load packages following \texttt{luamml}'s
% naming scheme for these patch packages.
% \begin{macrocode}
\cs_new_protected:Npn \__luamml_patch_package:nn #1 #2 {
\@ifpackageloaded {#1} {#2} {
\hook_gput_code:nnn {package/#1/after} {luamml} {#2}
}
}
\cs_new_protected:Npn \__luamml_patch_package:n #1 {
\__luamml_patch_package:nn {#1} {
\RequirePackage { luamml-patches-#1 }
}
}
% \end{macrocode}
% \end{macro}
%
% We currently provide minimal patching for the kernel, \pkg{amsmath} and \pkg{array}.
% Currently only the kernel code supports pdf\TeX, but it's planned to extend this.
% \begin{macrocode}
\RequirePackage { luamml-patches-kernel }
%<*luatex>
\__luamml_patch_package:n {amstext}
\__luamml_patch_package:n {amsmath}
\__luamml_patch_package:n {mathtools}
\__luamml_patch_package:n {array}
%</luatex>
% \end{macrocode}
% \iffalse
%</package>
% \fi
% \end{implementation}
% \Finale

32
luamml.sty Normal file
View File

@ -0,0 +1,32 @@
\ProvidesExplPackage {luamml} {2021-04-23} {0.0.1-alpha}
{Feel free to add a description here}
\int_new:N \l__luamml_flag_int
\int_new:N \tracingmathml
\lua_now:n { require'luamml-tex' }
\cs_new:Nn \luamml_flag_save: {
\int_set:Nn \l__luamml_flag_int { 2 }
}
\cs_new:Nn \luamml_flag_ignore: {
\int_set:Nn \l__luamml_flag_int { 3 }
}
\cs_new:Nn \luamml_flag_alignment_left: {
\int_set:Nn \l__luamml_flag_int { 7 }
}
\cs_new:Nn \luamml_flag_alignment_right: {
\int_set:Nn \l__luamml_flag_int { 6 }
}
\cs_new:Npn \__luamml_patch_package:nn #1 #2 {
\@ifpackageloaded {#1} {#2} {
\hook_gput_code:nnn {package/after/#1} {luamml} {#2}
}
}
\cs_new:Npn \__luamml_patch_package:n #1 {
\__luamml_patch_package:nn {#1} {
\RequirePackage { luamml-patches-#1 }
}
}
\__luamml_patch_package:n {amsmath}
\__luamml_patch_package:n {array}

View File

@ -1,39 +0,0 @@
local properties = {}
local subtypes = {
noad = {[0] = 'ord', 'opdisplaylimits', 'oplimits', 'opnolimits', 'bin', 'rel', 'open', 'close', 'punct', 'inner', 'under', 'over', 'vcenter'},
fence = {[0] = 'unset', 'left', 'middle', 'right', 'no'},
radical = {[0] = 'radical', 'uradical', 'uroot', 'uunderdelimiter', 'uoverdelimiter', 'udelimiterunder', 'udelimiterover'},
}
local function traverse_iter(context, head)
if head == nil then
head = context
else
head = head.next
end
if head then
return head, head.id, head.subtype
else
return nil
end
end
node = {
get_properties_table = function()
return properties
end,
id = function(name)
return name
end,
is_node = function(node)
return type(node) == 'table' and node.id and true or false
end,
subtypes = function(id)
return subtypes[id]
end,
traverse = function(head)
return traverse_iter, head, nil
end,
direct = {
todirect = function(n) return n end,
},
}
tex.nulldelimiterspace = tex.nulldelimiterspace or 78643 -- 1.2pt

View File

@ -1,61 +0,0 @@
local l = lpeg or require'lpeg'
local line = (1-l.P'\n')^0 * '\n'
local id = l.R'09'^1/tonumber
local non_final_list_block = (l.C((1-l.P'\n')^1) * '\n' - '### ' + '\n')^0
local math_lists_block = l.Ct('### ' * l.Cg(l.C'display' * ' ', 'display')^-1 * 'math mode entered at line ' * l.Cg(l.R'09'^1 / tonumber, 'line') * '\n'
* non_final_list_block)^1
local generic_list_block = '### ' * (line - 'current page:') * non_final_list_block
local luamml_block = l.Cg('LUAMML_FORMULA_BEGIN:' * id * ':' * l.Ct(
l.Cg(id, 'flag') * ':' * l.Cg((1-l.S':\n')^0, 'tag') * ':' * l.Cg((1-l.P'\n')^1, 'label')^-1 * l.P'\n'^1
* (math_lists_block + generic_list_block/0)^0
* (line - 'LUAMML_FORMULA_END\n')^0
* 'LUAMML_FORMULA_END\n') * l.Cc'groups')
local luamml_mark = l.Cg('LUAMML_MARK:' * id * ':' * l.Cs((1 - l.P'\n' + l.Cg('\n' * l.Cc'' - '\nLUAMML_MARK_END\n'))^0) * '\nLUAMML_MARK_END\n' * l.Cc'marks')
local function add(a, b) return a + b end
local count_block = '### ' * line * l.Cf(l.Cc(0) * (('\\' * l.Cc(1))^-1 * line - '### ')^0, add)
local luamml_precount = l.Cg('LUAMML_COUNT:' * id * l.P'\n'^1
* count_block * l.Cc'precount')
local luamml_postcount = l.Cg('LUAMML_COUNT_END:' * id * l.P'\n'^1
* count_block * l.Cc'postcount')
local luamml_instruction = l.Cg('LUAMML_INSTRUCTION:' * l.Cc(nil) * l.C((1 - l.P'\n')^0) * '\n' * l.Cc'instructions')
local function multi_table_set(t, key, value, table)
table = t[table]
table[key or #table + 1] = value
return t
end
local log_file = l.Cf(l.Ct(l.Cg(l.Ct'', 'groups')
* l.Cg(l.Ct'', 'precount')
* l.Cg(l.Ct'', 'postcount')
* l.Cg(l.Ct'', 'marks')
* l.Cg(l.Ct'', 'instructions'))
* (luamml_block + luamml_mark + luamml_instruction + luamml_precount + luamml_postcount + line)^0,
multi_table_set)
return function(filename)
local f
if filename and filename ~= '-' then
local msg f, msg = assert(io.open(filename, 'r'))
if not f then return f, msg end
end
local content = (f or io.stdin):read'a'
if f then f:close() end
-- The following does *not* end with * -1 since we want to allow the last line to not end with \n.
-- In that case we ignore the last line, but that's safe since the last line never contains our markers.
local parsed = assert(log_file:match(content))
local precount, postcount, count = parsed.precount, parsed.postcount, {}
for id, pre in next, precount do
local post = assert(postcount[id], 'Unbalanced count')
count[id], postcount[id] = post-pre, nil
end
assert(not next(postcount), 'Unbalanced count')
parsed.precount, parsed.postcount, parsed.count = nil, nil, count
return parsed
end

View File

@ -1,234 +0,0 @@
require'pdfmml-emulate-node'
local properties = node.get_properties_table()
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)
local scaled = l.P'-'^-1 * l.R'09'^1 * '.' * l.R'09'^1 / function(s) return (tonumber(s * 0x10000) + .5) // 1 end
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)
local delimiter_code = '"' * (l.R('09', 'AF')^1 / function(s)
local code = tonumber(s, 16)
return {id = 'delim',
small_fam = (code >> 20) & 0xF,
small_char = (code >> 12) & 0xFF,
large_fam = (code >> 8) & 0xF,
large_char = code & 0xFF,
}
end)
local balanced_braces = l.Ct{'{' * (1-l.S'{}'+l.V(1))^0 * '}'}
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'))
local hdw = '(' * l.Cg(scaled + '*' * l.Cc(-0x40000000), 'height') * '+' * l.Cg(scaled + '*' * l.Cc(-0x40000000), 'depth') * ')x' * l.Cg(scaled + '*' * l.Cc(-0x40000000), 'width')
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('(\\' * (
'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'))
+ 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
local simple_noad = l.Ct('\\' * (
'math' * l.Cg(
'ord' * l.Cc(0)
+ 'open' * l.Cc(6)
+ 'op\\limits' * l.Cc(2)
+ 'op\\nolimits' * l.Cc(3)
+ 'op' * l.Cc(1)
+ 'bin' * l.Cc(4)
+ 'rel' * l.Cc(5)
+ 'close' * l.Cc(7)
+ 'punct' * l.Cc(8)
+ 'inner' * l.Cc(9)
+ 'under' * l.Cc(10)
+ 'over' * l.Cc(11)
+ 'vcenter' * l.Cc(12)
, 'subtype') * l.Cg(l.Cc'noad', 'id')
+ 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')
* 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')
+ l.Cg(
'display' * l.Cc(0)
+ 'text' * l.Cc(2)
+ 'scriptscript' * l.Cc(6)
+ 'script' * l.Cc(4), 'subtype') * l.Cg('style', 'id')
+ '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
+ generic_simple_node
local simple_text = l.Ct('\\' * (
l.Cg('math', 'id') * l.Cg(
'on' * l.Cc(0)
+ 'off' * l.Cc(1)
, 'subtype') * l.Cg(', surrounded ' * scaled + l.Cc(0), 'surround')
)) * -1
+ generic_simple_node
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
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'))
* -1
local mathchoice_noad = l.Ct('\\mathchoice' * l.Cg(l.Cc'choice', 'id') * -1)
local mark_whatsit = '\\write' * ('-' + l.R'09'^1) * '{LUAMML_MARK_REF:' * (l.R'09'^1/tonumber) * ':'
local parse_list
local function parse_kernel(lines, i, prefix, parsed)
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
if box_node:match(lines[i], #prefix + 1) then return skip_list(lines, i+1, prefix .. '.') end
result, i = parse_list(lines, i, prefix, parsed)
return {list = result, id = 'sub_mlist'}, i
end
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
function parse_list(lines, i, prefix, parsed)
i = i or 1
prefix = prefix or ''
local head, last
local mark_environment = {data = parsed,}
local current_mark, current_count, current_offset
while true do
local skip
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
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)
if last then
simple.prev, last.next = last, simple
end
last = simple
else
local fraction = fraction_noad:match(line, #prefix+1)
if fraction then
fraction.num, i = parse_kernel(lines, i + 1, prefix .. '\\', parsed)
fraction.denom, i = parse_kernel(lines, i, prefix .. '/', parsed)
if last then
fraction.prev, last.next = last, fraction
end
last = fraction
else
local mathchoice = mathchoice_noad:match(line, #prefix+1)
if mathchoice then
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)
if last then
mathchoice.prev, last.next = last, mathchoice
end
last = mathchoice
else
skip = true
local mark = mark_whatsit:match(line, #prefix+1)
if mark then
local mark_table = assert(load('return {' .. assert(parsed.marks[mark], 'Undefined mark encountered') .. '}', nil, 't', mark_environment))()
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
i = i + 1
else
print(line, prefix, i)
print('unknown noad ' .. line:sub(#prefix+1))
i = i + 1
end
end
end
end
if not head then head = last end
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
end
return head, i
end
return parse_list

View File

@ -1,96 +0,0 @@
#!/usr/bin/env texlua
require'pdfmml-emulate-node'
local convert = require'luamml-convert'
local mappings = require'luamml-legacy-mappings'
local to_xml = require'luamml-xmlwriter'
local parse_showlists = require'pdfmml-showlists'
local parse_log = require'pdfmml-logreader'
local text_families = {}
local attributes = lfs.attributes
local function try_extensions_(base, extension, ...)
if extension == nil then return end
local fullname = base .. extension
if attributes(fullname, 'mode') == 'file' then
return fullname
end
return try_extensions_(base, ...)
end
local function try_extensions(base, ...)
if attributes(base, 'mode') == 'file' then return base end
return try_extensions_(base, ...)
end
if #arg < 1 then
io.stderr:write(string.format('Usage: %s {logname} [{outname}]\n\z
If {outname} includes {}, then a separate file is written for every formula with {} replaced by the formula id.\n', arg[0]))
os.exit(1)
end
local parsed = assert(parse_log(assert(try_extensions(arg[1], '.tml', '.log'),
"Couldn't find input file.")))
for i, inst in ipairs(parsed.instructions) do
local _, _, family, mapping_name = inst:find'^REGISTER_MAPPING:([0-9]+):(.*)$'
if family then
local mapping = mappings[mapping_name]
if mapping then
convert.register_family(tonumber(family), mapping)
else
io.stderr:write(string.format('Unknown mapping %s ignored\n', mapping_name))
end
else
io.stderr:write'Unknown instruction ignored\n'
end
end
local out_prefix, out_suffix, out_stream
if not arg[2] or arg[2] == '-' then
out_stream = io.stdout
else
local _ _, _, out_prefix, out_suffix = arg[2]:find'^(.*){}(.*)$'
if not out_prefix then
out_stream = assert(io.open(arg[2], 'w'))
end
end
parsed.mathml = {}
local function shallow_copy(t)
local new = {}
for k, v in next, t do
new[k] = v
end
return new
end
-- Currently only 3 flag values are supported:
-- 0: Ignore (Doesn't make a lot of sense here)
-- 1: Only save
-- 3: Generate normally
for i, block in ipairs(parsed.groups) do
local flag, tag, label = block.flag, block.tag, block.label
block = block[1]
if flag & 3 ~= 0 then
local style = flag & 16 == 16 and flag>>5 & 0x7 or block.display and 0 or 2
local xml = convert.process(parse_showlists(block, nil, nil, parsed), style, text_families)
if flag & 2 == 2 then
local stream = out_stream or assert(io.open(out_prefix .. tostring(i) .. out_suffix, 'w'))
stream:write(to_xml(convert.make_root(shallow_copy(xml), style)), '\n')
if not out_stream then stream:close() end
end
if tag ~= 'mrow' then
if xml[0] == 'mrow' then
xml[0] = tag
else
xml = {[0] = tag, xml}
end
end
if (not block.display) and flag & 1 == 1 and label then
if parsed.mathml[label] then
error'Invalid label reuse'
end
parsed.mathml[label] = xml
end
end
end

View File

@ -1,98 +0,0 @@
% \newcommand\Luamml{\pkg{Luamml}}
% \newcommand\luamml{\pkg{luamml}}
% \newcommand\xmltag[1]{\texttt{<#1>}}
% \section{\Luamml's representation of XML and MathML}
% In the following I assume basic familiarity with both Lua\TeX's representation of math noads and MathML.
%
% \subsection{Representation of XML elements}
% In many places, \luamml\ passes around XML elements. Every element is represented by a Lua table.
% Element \texttt 0 must always be present and is a string representing the tag name.
% The positive integer elements of the table represent child elements (either strings for direct text content or nested tables for nested elements).
% All string members which do not start with a colon are attributes, whose value is the result of applying \texttt{tostring} to the field value.
% This implies that these values should almost always be strings, except that the value \texttt 0 (since it never needs a unit) can often be set as a number.
% For example the XML document
% \begin{verbatim}
% <math block="display">
% <mn>0</mn>
% <mo> &lt; </mo>
% <mi mathvariant="normal">x</mi>
% </math>
% \end{verbatim}
% would be represented by the Lua table
% \begin{verbatim}
% {[0] = "math", block="display",
% {[0] = "mn", "0"},
% {[0] = "mo", "<"},
% {[0] = "mi", mathvariant="normal", "x"}
% }
% \end{verbatim}
%
% \subsection{Expression cores}
% MathML knows the concept of \enquote{embellished operators}:
% \begin{blockquote}
% The precise definition of an \enquote{embellished operator} is:
% \begin{itemize}
% \item an \xmltag{mo} element;
% \item or one of the elements \xmltag{msub}, \xmltag{msup}, \xmltag{msubsup}, \xmltag{munder}, \xmltag{mover}, \xmltag{munderover}, \xmltag{mmultiscripts}, \xmltag{mfrac}, or \xmltag{semantics} (§ 5.1 Annotation Framework), whose first argument exists and is an embellished operator;
% \item or one of the elements \xmltag{mstyle}, \xmltag{mphantom}, or \xmltag{mpadded}, such that an mrow containing the same arguments would be an embellished operator;
% \item or an \xmltag{maction} element whose selected sub-expression exists and is an embellished operator;
% \item or an \xmltag{mrow} whose arguments consist (in any order) of one embellished operator and zero or more space-like elements.
% \end{itemize}
% \end{blockquote}
% For every embellished operator, MathML calls the \xmltag{mo} element defining the embellished operator the \enquote{core} of the embellished operator.
%
% \Luamml\ makes this slightly more general: Every expression is represented by a pair of two elements: The expression and it's core.
% The core is always a \xmltag{mo}, \xmltag{mi}, or \xmltag{mn}, \texttt{nil} or s special marker for space like elements.
%
% If and only if the element is a embellished operator the core is a \xmltag{mo} element representing the core of the embellished operator.
% The core is a \xmltag{mi} or a \xmltag{mn} element if and only if the element would be an embellished operator with this core if this element where a \xmltag{mo} element.
% The core is the special space like marker for space like elements. Otherwise the core is \texttt{nil}.
%
% \subsection{Translation of math noads}
% A math lists can contain the following node types: noad, fence, fraction, radical, accent, style, choice, ins, mark, adjust, boundary, whatsit, penalty, disc, glue, and kern. The \enquote{noads}
%
% \subsubsection{Translation of kernel noads}
% The math noads of this list contain nested kernel noads. So in the first step, we look into how kernel nodes are translated to math nodes.
%
% \paragraph{\texttt{math_char} kernel noads}
% First the family and character value in the \texttt{math_char} are used to lookup the Unicode character value of this \texttt{math_char}.
% (For \texttt{unicode-math}, this is usually just the character value. Legacy maths has to be remapped based on the family.)
% Then there are two cases: The digits \texttt{0} to \texttt{9} are mapped to \xmltag{mn} elements, everything else becomes a \xmltag{mi} element with \texttt{mathvariant} set to \texttt{normal}.
% (The \texttt{mathvariant} value might get suppressed if the character defaults to mathvariant \texttt{normal}.)
% In either case, the \texttt{tex:family} attribute is set to the family number if it's not \texttt{0}.
%
% The core is always set to the expression itself. E.g.\ the \texttt{math_char} kernel noad \verb+\fam3 a+ would become (assuming no remapping for this family)
% \begin{verbatim}
% {[0] = 'mi',
% mathvariant = 'normal',
% ["tex:family"] = 3,
% "a"
% }
% \end{verbatim}
%
% \subsubsection{\texttt{sub_box} kernel noads}
% I am open to suggestions how to convert them properly.
%
% \subsubsection{\texttt{sub_mlist} kernel noads}
% The inner list is converted as a \xmltag{mrow} element, with the core being the core of the \xmltag{mrow} element. See the rules for this later.
%
% \subsubsection{\texttt{delim} kernel noads}
% If the \texttt{small_char} is zero, these get converted as space like elements of the form
% \begin{verbatim}
% {[0] = 'mspace',
% width = '1.196pt',
% }
% \end{verbatim}
% where 1.196 is replaced by the current value of \verb+\nulldelimiterspace+ converted to \texttt{bp}.
%
% Otherwise the same rules as for \texttt{math_char} apply,
% except that instead of \texttt{mi} or \xmltag{mn} elements,
% \texttt{mo} elements are generated,
% \texttt{mathvariant} is never set,
% \texttt{stretchy} is set to \texttt{true} if the operator is not on the list of default stretchy operators in the MathML specification
% nd \texttt{lspace} and \texttt{rspace} attributes are set to zero.
%
% \subsubsection{\texttt{acc} kernel noads}
% Depending on the surrounding element containing the \texttt{acc} kernel noad, it is either stretchy or not.
% If it's stretchy, the same rules as for \texttt{delim} apply, except that \texttt{lspace} and \texttt{rspace} are not set.
% Otherwise the \texttt{stretchy} attribute is set to false if the operator is on the list of default stretchy operators.

46
test_tex.tex Normal file
View File

@ -0,0 +1,46 @@
\documentclass{article}
\usepackage{luamml}
\usepackage{amsmath,array}
\usepackage{unicode-math}
%% Uncomment the following lines when used without unicode-math
% \RegisterFamilyMapping\symsymbols{oms}
% \RegisterFamilyMapping\symletters{oml}
% \RegisterFamilyMapping\symlargesymbols{omx}
\ExplSyntaxOn
\tracingmathml=2
\pdfvariable compresslevel=0
\cs_new_protected:Npn \ShowMathMLObj {
\message { \luamml_get_last_mathml_stream:e{}~0~R }
}
\ExplSyntaxOff
\begin{document}
\[
\begin{pmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{pmatrix}
=
\begin{cases}
1 & $if $a=b\\
2 & $else$
\end{cases}
\]
\[
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}.
\]
\[
\sum_a\underline c\dot bc'
\]
\begin{align}
abc&=def & e^{\mathrm{i}\pi}&=-1\\
1+2&=3\\
5
\end{align}
Es gilt $\sin(x)-\sin(x+2\pi)=0$.
\end{document}

View File

@ -1,145 +0,0 @@
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑎</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑏</mi>
</math>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mrow>
<mo fence="true" lspace="0" rspace="0" symmetric="true">(</mo>
<mspace width="-4.981pt" />
<mpadded lspace="+4.981pt" width="+9.963pt">
<mtable>
<mtr>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
</mtr>
</mtable>
</mpadded>
<mspace width="-4.981pt" />
<mo fence="true" lspace="0" rspace="0" symmetric="true">)</mo>
</mrow>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mrow>
<mo fence="true" lspace="0" rspace="0" symmetric="true">{</mo>
<mpadded lspace="+4.981pt" width="+9.963pt">
<mtable>
<mtr>
<mtd columnalign="left">
<mn>1</mn>
</mtd>
<mtd columnalign="left">
<mtext>if&#160;
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑎</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑏</mi>
</math></mtext>
</mtd>
</mtr>
<mtr>
<mtd columnalign="left">
<mn>2</mn>
</mtd>
<mtd columnalign="left">
<mtext>else</mtext>
</mtd>
</mtr>
</mtable>
</mpadded>
<mspace width="1.196pt" />
</mrow>
</math>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑥</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mfrac>
<mrow>
<mo lspace="0" rspace="0.222em"></mo>
<mi>𝑏</mi>
<mo lspace="0.222em" rspace="0.222em">±</mo>
<msqrt>
<mrow>
<msup>
<mi>𝑏</mi>
<mn>2</mn>
</msup>
<mo lspace="0.222em" rspace="0.222em"></mo>
<mn>4</mn>
<mi>𝑎</mi>
<mi>𝑐</mi>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn>
<mi>𝑎</mi>
</mrow>
</mfrac>
<mo lspace="0" rspace="0">.</mo>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑏</mi>
</math>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<munder>
<mo lspace="0" movablelimits="true" rspace="0.167em">∑</mo>
<mi>𝑎</mi>
</munder>
<munder>
<mi>𝑐</mi>
<mo>_</mo>
</munder>
<mover>
<mi>𝑏</mi>
<mo stretchy="false">_</mo>
</mover>
<msup>
<mi>𝑐</mi>
<mi mathvariant="normal"></mi>
</msup>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi mathvariant="normal">sin</mi>
<mo lspace="0" rspace="0" stretchy="false">(</mo>
<mi>𝑥</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.222em" rspace="0.222em"></mo>
<mi mathvariant="normal">sin</mi>
<mo lspace="0" rspace="0" stretchy="false">(</mo>
<mi>𝑥</mi>
<mo lspace="0.222em" rspace="0.222em">+</mo>
<mn>2</mn>
<mi>𝜋</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mn>0</mn>
</math>

View File

@ -1,39 +0,0 @@
\DocumentMetadata{
uncompress,
pdfversion = 2.0,
testphase = phase-I,
}
\input{regression-test}
\documentclass{article}
\usepackage[l3build]{luamml-demo}
\begin{document}
\tagstructbegin{tag=Document}
\LuaMMLTagAF{} {
\[
\left(\begin{matrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{matrix}\right)
=
\begin{cases}
1 & \mbox{if $a=b$}\\
2 & \mbox{else}
\end{cases}
\]
}
\LuaMMLTagAF{} {
\[
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}.
\]
}
\LuaMMLTagAF{} {
\[
\sum_a\underline c\dot bc'
\]
}
Es gilt \LuaMMLTagAF{}{$\sin(x)-\sin(x+2\pi)=0$}.
\tagstructend
\end{document}

View File

@ -1,59 +0,0 @@
\ExplSyntaxOn
\sys_gset_rand_seed:n{42}
\ExplSyntaxOff
\DocumentMetadata{
uncompress,
pdfversion = 2.0,
testphase = phase-I,
}
\input{regression-test}
\documentclass{article}
\usepackage[structelem]{luamml-demo}
\usepackage{unicode-math}
\begin{document}
\tagstructbegin{tag=Document}
\tagstructbegin{tag=P}
\tagmcbegin{tag=P}
hello
\tagmcend
\tagstructend
\LuaMMLTagAF{} {
\[
\begin{pmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{pmatrix}
=
\begin{cases}
1 & \text{if $a=b$} \\
2 & \text{else}
\end{cases}
\]
}
\LuaMMLTagAF{} {
\[
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}.
\]
}
\LuaMMLTagAF{} {
\[
\sum_a\underline c\dot bc'
\]
}
\LuaMMLTagAF{} {
\begin{align}
abc&=def & e^{\mathrm{i}\pi}&=-1\\
\Big(1+2&=3\Big)\\
5
\end{align}
}
Es gilt \LuaMMLTagAF{}{$\sin(x)-\sin(x+2\pi)=0$}.
\tagstructend
\end{document}

File diff suppressed because it is too large Load Diff

View File

@ -1,237 +0,0 @@
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mstyle displaystyle="true" scriptlevel="0">
<mi>𝑎</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑏</mi>
</mstyle>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑎</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑏</mi>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑎</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑏</mi>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑎</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑏</mi>
</math>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mrow>
<mo fence="true" lspace="0" rspace="0" symmetric="true">(</mo>
<mspace width="-4.981pt" />
<mpadded lspace="+4.981pt" width="+9.963pt">
<mtable>
<mtr>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
</mtr>
<mtr>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>0</mn>
</mtd>
<mtd>
<mn>1</mn>
</mtd>
</mtr>
</mtable>
</mpadded>
<mspace width="-4.981pt" />
<mo fence="true" lspace="0" rspace="0" symmetric="true">)</mo>
</mrow>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mrow>
<mo fence="true" lspace="0" rspace="0" symmetric="true">{</mo>
<mpadded lspace="+4.981pt" width="+9.963pt">
<mtable>
<mtr>
<mtd columnalign="left">
<mn>1</mn>
</mtd>
<mtd columnalign="left">
<mtext>if&#160;
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑎</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑏</mi>
</math></mtext>
<mi />
</mtd>
</mtr>
<mtr>
<mtd columnalign="left">
<mn>2</mn>
</mtd>
<mtd columnalign="left">
<mtext>else</mtext>
<mi />
</mtd>
</mtr>
</mtable>
</mpadded>
<mspace width="1.196pt" />
</mrow>
</math>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mi>𝑥</mi>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mfrac>
<mrow>
<mo lspace="0" rspace="0.222em"></mo>
<mi>𝑏</mi>
<mo lspace="0.222em" rspace="0.222em">±</mo>
<msqrt>
<mrow>
<msup>
<mi>𝑏</mi>
<mn>2</mn>
</msup>
<mo lspace="0.222em" rspace="0.222em"></mo>
<mn>4</mn>
<mi>𝑎</mi>
<mi>𝑐</mi>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn>
<mi>𝑎</mi>
</mrow>
</mfrac>
<mo lspace="0" rspace="0">.</mo>
</math>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<munder>
<mo lspace="0" movablelimits="true" rspace="0.167em">∑</mo>
<mi>𝑎</mi>
</munder>
<munder>
<mi>𝑐</mi>
<mo>_</mo>
</munder>
<mover>
<mi>𝑏</mi>
<mo stretchy="true">.</mo>
</mover>
<msup>
<mi>𝑐</mi>
<mi mathvariant="normal"></mi>
</msup>
</math>
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mtable columnalign="right left right left" columnspacing="0 .8em 0" displaystyle="true">
<mtr>
<mtd intent=":equationlabel">
<mtext>(1)</mtext>
</mtd>
<mtd>
<mi>𝑎</mi>
<mi>𝑏</mi>
<mi>𝑐</mi>
</mtd>
<mtd>
<mi />
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mi>𝑑</mi>
<mi>𝑒</mi>
<mi>𝑓</mi>
</mtd>
<mtd>
<msup>
<mi>𝑒</mi>
<mrow>
<mi mathvariant="normal">i</mi>
<mi>𝜋</mi>
</mrow>
</msup>
</mtd>
<mtd>
<mi />
<mo lspace="0.278em" rspace="0">=</mo>
<mo lspace="0.278em" rspace="0"></mo>
<mn>1</mn>
</mtd>
</mtr>
<mtr>
<mtd intent=":equationlabel">
<mtext>(2)</mtext>
</mtd>
<mtd>
<mo fence="true" lspace="0" maxsize="17.861pt" minsize="17.861pt" rspace="0" symmetric="true">(</mo>
<mn>1</mn>
<mo lspace="0.222em" rspace="0.222em">+</mo>
<mn>2</mn>
</mtd>
<mtd>
<mi />
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mn>3</mn>
<mo fence="true" lspace="0" maxsize="17.861pt" minsize="17.861pt" rspace="0" symmetric="true">)</mo>
</mtd>
<mtd>
<mi />
</mtd>
<mtd>
<mi />
</mtd>
</mtr>
<mtr>
<mtd intent=":equationlabel">
<mtext>(3)</mtext>
</mtd>
<mtd>
<mn>5</mn>
</mtd>
<mtd>
<mi />
</mtd>
<mtd>
<mi />
</mtd>
<mtd>
<mi />
</mtd>
</mtr>
</mtable>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mi mathvariant="normal">sin</mi>
<mo lspace="0" rspace="0" stretchy="false">(</mo>
<mi>𝑥</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.222em" rspace="0.222em"></mo>
<mi mathvariant="normal">sin</mi>
<mo lspace="0" rspace="0" stretchy="false">(</mo>
<mi>𝑥</mi>
<mo lspace="0.222em" rspace="0.222em">+</mo>
<mn>2</mn>
<mi>𝜋</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mn>0</mn>
</math>

View File

@ -1,49 +0,0 @@
\DocumentMetadata{
uncompress,
pdfversion = 2.0,
testphase = phase-I,
}
\input{regression-test}
\documentclass{article}
\usepackage[l3build]{luamml-demo}
\usepackage{unicode-math}
\begin{document}
\tagstructbegin{tag=Document}
\LuaMMLTagAF{} {
\[
\begin{pmatrix}
1 & 0 & 0 \\
0 & 1 & 0 \\
0 & 0 & 1
\end{pmatrix}
=
\begin{cases}
1 & \text{if $a=b$}\\
2 & \text{else}
\end{cases}
\]
}
\LuaMMLTagAF{} {
\[
x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}.
\]
}
\LuaMMLTagAF{} {
\[
\sum_a\underline c\dot bc'
\]
}
\LuaMMLTagAF{} {
\begin{align}
abc&=def & e^{\mathrm{i}\pi}&=-1\\
\Big(1+2&=3\Big)\\
5
\end{align}
}
Es gilt \LuaMMLTagAF{}{$\sin(x)-\sin(x+2\pi)=0$}.
\tagstructend
\end{document}

View File

@ -1,51 +0,0 @@
<math display="block" xmlns="http://www.w3.org/1998/Math/MathML">
<mi mathvariant="normal">∅</mi>
<mo lspace="0.278em" rspace="0">⊧</mo>
<mo lspace="0.278em" rspace="0" stretchy="false">(</mo>
<mo lspace="0" rspace="0" stretchy="false">(</mo>
<mi>𝑝</mi>
<mo lspace="0.278em" rspace="0.278em" stretchy="false">⇒</mo>
<mi>𝑞</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.278em" rspace="0" stretchy="false">⇒</mo>
<mo lspace="0.278em" rspace="0" stretchy="false">(</mo>
<mi>𝑞</mi>
<mo lspace="0.278em" rspace="0.278em" stretchy="false">⇒</mo>
<mi>𝑟</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.278em" rspace="0" stretchy="false">⇒</mo>
<mo lspace="0.278em" rspace="0" stretchy="false">(</mo>
<mi>𝑝</mi>
<mo lspace="0.278em" rspace="0" stretchy="false">⇒</mo>
<mo lspace="0.278em" rspace="0" stretchy="false">(</mo>
<mi>𝑞</mi>
<mo lspace="0.278em" rspace="0.278em" stretchy="false">⇒</mo>
<mi>𝑟</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
</math>
<math xmlns="http://www.w3.org/1998/Math/MathML">
<mrow>
<mi mathvariant="normal">s</mi>
<mi mathvariant="normal">i</mi>
<mi mathvariant="normal">n</mi>
</mrow>
<mo lspace="0" rspace="0" stretchy="false">(</mo>
<mi>𝑥</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.222em" rspace="0.222em"></mo>
<mrow>
<mi mathvariant="normal">s</mi>
<mi mathvariant="normal">i</mi>
<mi mathvariant="normal">n</mi>
</mrow>
<mo lspace="0" rspace="0" stretchy="false">(</mo>
<mi>𝑥</mi>
<mo lspace="0.222em" rspace="0.222em">+</mo>
<mn>2</mn>
<mi>𝜋</mi>
<mo lspace="0" rspace="0" stretchy="false">)</mo>
<mo lspace="0.278em" rspace="0.278em">=</mo>
<mn>0</mn>
</math>

View File

@ -1,20 +0,0 @@
\documentclass{article}
\usepackage{amsmath}
\usepackage{luamml-demo}
\begin{document}
\[
\emptyset\models((p\Rightarrow q)\Rightarrow(q\Rightarrow r))\Rightarrow (p\Rightarrow (q\Rightarrow r))
\WriteoutFormula
\]
%\begin{align}
% abc&=def & e^{\mathrm{i}\pi}&=-1\\
% \Big(1+2&=3\Big)\\
% &4\\
% 5
%\end{align}
Es gilt $\sin(x)-\sin(x+2\pi)=0\WriteoutFormula$.
\end{document}

View File

@ -1,218 +0,0 @@
LUAMML_INSTRUCTION:REGISTER_MAPPING:2:oms
LUAMML_INSTRUCTION:REGISTER_MAPPING:1:oml
LUAMML_INSTRUCTION:REGISTER_MAPPING:3:omx
LUAMML_MARK:1:count=3,nucleus=true,core={[0]='mi','\u{22a7}'},
LUAMML_MARK_END
LUAMML_FORMULA_BEGIN:1:3:mrow:
### display math mode entered at line 6
\mathord
.\fam2 ;
\write3{LUAMML_MARK_REF:1:}
\mathrel
.\fam2 j
\mathrel
.\mkern-3.0mu
\mathrel
.\fam0 =
\mathopen
.\fam0 (
\mathopen
.\fam0 (
\mathord
.\fam1 p
\mathrel
.\fam2 )
\mathord
.\fam1 q
\mathclose
.\fam0 )
\mathrel
.\fam2 )
\mathopen
.\fam0 (
\mathord
.\fam1 q
\mathrel
.\fam2 )
\mathord
.\fam1 r
\mathclose
.\fam0 )
\mathclose
.\fam0 )
\mathrel
.\fam2 )
\mathopen
.\fam0 (
\mathord
.\fam1 p
\mathrel
.\fam2 )
\mathopen
.\fam0 (
\mathord
.\fam1 q
\mathrel
.\fam2 )
\mathord
.\fam1 r
\mathclose
.\fam0 )
\mathclose
.\fam0 )
### vertical mode entered at line 0
### current page:
\write-{}
\glue(\topskip) 10.0
\hbox(0.0+0.0)x345.0, glue set 330.0fil
.\hbox(0.0+0.0)x15.0
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
total height 10.0
goal height 550.0
prevdepth 0.0, prevgraf 1 line
! OK
LUAMML_FORMULA_END
LUAMML_FORMULA_BEGIN:2:3:mrow:
### math mode entered at line 18
\mathop\nolimits
.\kern 0.0
.\mathord
..\fam0 s
.\mathord
..\fam0 i
.\mathord
..\fam0 n
\mathopen
.\fam0 (
\mathord
.\fam1 x
\mathclose
.\fam0 )
\mathbin
.\fam2 ^^@
\mathop\nolimits
.\kern 0.0
.\mathord
..\fam0 s
.\mathord
..\fam0 i
.\mathord
..\fam0 n
\mathopen
.\fam0 (
\mathord
.\fam1 x
\mathbin
.\fam0 +
\mathord
.\fam0 2
\mathord
.\fam1 ^^Y
\mathclose
.\fam0 )
\mathrel
.\fam0 =
\mathord
.\fam0 0
### horizontal mode entered at line 18
\hbox(0.0+0.0)x15.0
\OT1/cmr/m/n/10 E
\OT1/cmr/m/n/10 s
\glue 3.33333 plus 1.66666 minus 1.11111
\OT1/cmr/m/n/10 g
\OT1/cmr/m/n/10 i
\OT1/cmr/m/n/10 l
\OT1/cmr/m/n/10 t
\glue 3.33333 plus 1.66666 minus 1.11111
spacefactor 1000
### vertical mode entered at line 0
### current page:
\write-{}
\glue(\topskip) 10.0
\hbox(0.0+0.0)x345.0, glue set 330.0fil
.\hbox(0.0+0.0)x15.0
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
\penalty 10000
\glue(\abovedisplayshortskip) 0.0 plus 3.0
\glue(\baselineskip) 4.5
\hbox(7.5+2.5)x185.77597, shifted 79.61201, display
.\OMS/cmsy/m/n/10 ;
.\write3{LUAMML_MARK_REF:1:}
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OMS/cmsy/m/n/10 j
.\hbox(0.0+0.0)x-1.66663
..\kern -1.66663
.\OT1/cmr/m/n/10 =
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OT1/cmr/m/n/10 (
.\OT1/cmr/m/n/10 (
.\OML/cmm/m/it/10 p
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OMS/cmsy/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OML/cmm/m/it/10 q
.\kern0.35878
.\OT1/cmr/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OMS/cmsy/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OT1/cmr/m/n/10 (
.\OML/cmm/m/it/10 q
.\kern0.35878
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OMS/cmsy/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OML/cmm/m/it/10 r
.\kern0.27779
.\OT1/cmr/m/n/10 )
.\OT1/cmr/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OMS/cmsy/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OT1/cmr/m/n/10 (
.\OML/cmm/m/it/10 p
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OMS/cmsy/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OT1/cmr/m/n/10 (
.\OML/cmm/m/it/10 q
.\kern0.35878
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OMS/cmsy/m/n/10 )
.\glue(\thickmuskip) 2.77771 plus 2.77771
.\OML/cmm/m/it/10 r
.\kern0.27779
.\OT1/cmr/m/n/10 )
.\OT1/cmr/m/n/10 )
\penalty 0
\glue(\belowdisplayshortskip) 6.0 plus 3.0 minus 3.0
\glue(\parskip) 0.0 plus 1.0
\glue(\parskip) 0.0
total height 30.5 plus 7.0 minus 3.0
goal height 550.0
prevdepth 2.5
! OK
LUAMML_FORMULA_END
LUAMML_MARK_REF:1:

View File

@ -1,20 +0,0 @@
\documentclass{article}
\usepackage{amsmath}
\usepackage{luamml-demo}
\begin{document}
\[
\emptyset\models((p\Rightarrow q)\Rightarrow(q\Rightarrow r))\Rightarrow (p\Rightarrow (q\Rightarrow r))
\WriteoutFormula
\]
%\begin{align}
% abc&=def & e^{\mathrm{i}\pi}&=-1\\
% \Big(1+2&=3\Big)\\
% &4\\
% 5
%\end{align}
Es gilt $\sin(x)-\sin(x+2\pi)=0\WriteoutFormula$.
\end{document}