Compare commits

..

128 Commits

Author SHA1 Message Date
Marcel Fabian Krüger
4849ee4373 Update version 2024-10-30 00:40:17 +01:00
Marcel Fabian Krüger
8389cd1a9d GH Actions setup 2024-10-29 07:31:03 +01:00
Marcel Fabian Krüger
3b6955314f Test file adjustments 2024-10-29 06:21:23 +01:00
Ulrike Fischer
17d7ebd16b use \DocumentMetadata 2024-10-28 12:01:05 +01:00
Ulrike Fischer
f71c7b3c67 add mathtools patch, latex3/tagging-project#734 2024-10-26 09:32:05 +02:00
Ulrike Fischer
dc2d92e3fd rename commands as discussed 2024-10-26 09:32:05 +02:00
Marcel Fabian Krüger
58ad8a5766 Support \smash and cleanup 2024-10-24 23:45:07 +02:00
Marcel Fabian Krüger
fc430b31ac Mark equation labels through intent 2024-10-20 12:21:51 +02:00
Marcel Fabian Krüger
cfb5d03b92 Fix MathML namespace in structure elements and improve error handling 2024-10-20 12:16:10 +02:00
Marcel Fabian Krüger
e5d5ac2efe Unbreak explicitly suppressing MathML conversion of elements 2024-10-20 00:58:08 +02:00
Marcel Fabian Krüger
071cf90549 Fix formatting 2024-10-19 10:09:17 +02:00
Marcel Fabian Krüger
92d3b9bd5b Adapt array patch for new array version 2024-08-24 14:29:20 +02:00
Marcel Fabian Krüger
9c757878ee Fix typos 2024-08-15 20:30:04 +02:00
Marcel Fabian Krüger
2656c86158 Remove old test files 2024-08-15 20:22:04 +02:00
Marcel Fabian Krüger
bae4f782bb Prepare for first upload 2024-08-15 20:19:16 +02:00
Marcel Fabian Krüger
1a19e3947a Add \l__luamml_pretty_int to control prettyprinting 2024-08-14 00:30:56 +02:00
Marcel Fabian Krüger
62bab63177 Drop \show statements 2024-07-18 12:53:06 +02:00
Marcel Fabian Krüger
413ec0112a Stop emitting intent="@ignore" 2024-07-17 19:36:49 +02:00
Marcel Fabian Krüger
e74bca7eac Add patches for latex-lab math compatibility 2024-07-16 09:11:07 +02:00
Marcel Fabian Krüger
be474c633a Fix documentation 2024-04-04 16:10:35 +02:00
Marcel Fabian Krüger
7341dd5fc6 Improve detection 2023-12-27 18:14:46 +01:00
Marcel Fabian Krüger
f4c0721401 Reset family assignments between math blocks 2023-12-27 17:48:39 +01:00
Marcel Fabian Krüger
3c033069f3 Automatically try to detect text fonts 2023-12-27 15:34:07 +01:00
Marcel Fabian Krüger
e6d7b73beb Make text_families more dynamic 2023-12-27 13:30:40 +01:00
Marcel Fabian Krüger
9b85cdb610 Don't serialize namespaced attributes into XML 2023-12-23 11:55:31 +01:00
Marcel Fabian Krüger
9c43b61387 Remove debugging output 2023-12-21 02:58:13 +01:00
Marcel Fabian Krüger
ae911c29ae Avoid attaching invalid attributes 2023-12-21 02:31:11 +01:00
Marcel Fabian Krüger
174cad050b Add LaTeX hook to intercept converted MathML 2023-12-20 19:22:21 +01:00
Marcel Fabian Krüger
637dfbf90a Escaping control characters 2023-12-19 23:27:31 +01:00
Marcel Fabian Krüger
89f8e2a79a Fix units for height and width in mspace 2023-12-19 21:35:33 +01:00
Marcel Fabian Krüger
7a52f5580b Fix XML escaping 2023-12-19 20:55:13 +01:00
Marcel Fabian Krüger
9005c304d7 Don't try to use expl3 box functions to access old 2e boxes in patches 2023-12-01 17:52:46 +01:00
Marcel Fabian Krüger
3bd184a3d1 Note potential mismatch in legacy mappings 2023-12-01 17:48:31 +01:00
Marcel Fabian Krüger
e83622cbae Recreate tests 2023-03-18 00:01:27 +01:00
Marcel Fabian Krüger
e84079ee73 Update preambles 2022-12-06 07:21:59 +01:00
Marcel Fabian Krüger
a2fb445894 Some test updates 2022-12-04 08:41:45 +01:00
Marcel Fabian Krüger
cb2c48aa8d Move {cases} patch to amsmath patches since it patches the amsmath definition 2022-12-04 07:51:36 +01:00
Marcel Fabian Krüger
f5be988925 Adapt to L3PL naming change of \use(_i):n 2022-12-04 07:33:13 +01:00
Marcel Fabian Krüger
d2bb7a9457 Reference demo package in README 2021-12-17 08:37:21 +01:00
Marcel Fabian Krüger
bdbeb16c53 Adapt to tagpdf's push/pop mc commands 2021-12-16 18:18:31 +01:00
Marcel Fabian Krüger
c8c32e0f4c Move cases patches to kernel patches 2021-11-02 02:27:20 +01:00
Marcel Fabian Krüger
d6758fe997 Beginning of draft for algorithm description 2021-11-01 07:44:40 +01:00
Marcel Fabian Krüger
288af48597 Update testfiles 2021-11-01 06:54:06 +01:00
Marcel Fabian Krüger
0cd0784699 Patch {cases} 2021-11-01 06:54:06 +01:00
Marcel Fabian Krüger
f132e3496a Patch \text 2021-11-01 06:54:06 +01:00
Marcel Fabian Krüger
c6a180cd1a Use Attribute NameSpace and add missing test files 2021-10-31 23:12:42 +01:00
Marcel Fabian Krüger
d1ef3292ee Adapt to new tagpdf versions 2021-10-31 17:52:34 +01:00
Marcel Fabian Krüger
8ddc9aeebc l3build for Lua testing 2021-10-31 17:18:23 +01:00
Marcel Fabian Krüger
cfae79975e Use l3build for testing 2021-10-31 16:06:29 +01:00
Marcel Fabian Krüger
5b9066eb26 Normalize parser 2021-07-04 03:18:36 +02:00
Marcel Fabian Krüger
54ce65a04d Add luamml-pdf-demo 2021-07-04 03:17:37 +02:00
Marcel Fabian Krüger
ddaf56b8af Fix typos 2021-07-04 01:37:17 +02:00
Marcel Fabian Krüger
71c127dd67 Extend showlists parser 2021-07-03 22:41:43 +02:00
Marcel Fabian Krüger
3eae23c268 Annotate composed kernel symbols 2021-07-03 06:19:53 +02:00
Marcel Fabian Krüger
235815eb98 Adapt phantom patches for pdfTeX 2021-06-28 06:40:14 +02:00
Marcel Fabian Krüger
5252cbc90d Add interfaces in LuaTeX for pdfTeX compatibility
This is a combination of 2 commits.

- Provide \luamml_pdf_write: dummy in LuaTeX
- Make labelled formulas accessible in LuaTeX annotations
2021-06-28 06:38:55 +02:00
Marcel Fabian Krüger
e76d9ec8bf Fix style flag handling 2021-06-28 06:38:55 +02:00
Marcel Fabian Krüger
e1b8debcf6 More reliable pdfTeX annotations 2021-06-27 05:39:42 +02:00
Marcel Fabian Krüger
d63bed65a1 More pdfTeX work 2021-06-27 04:30:49 +02:00
Marcel Fabian Krüger
e21116b501 Add labels to saved formulas 2021-06-27 00:50:13 +02:00
Marcel Fabian Krüger
e6077a2702 Fix flags 2021-06-26 22:38:24 +02:00
Marcel Fabian Krüger
75a03787f8 Apply flags and root tag in pdfTeX 2021-06-26 20:52:42 +02:00
Marcel Fabian Krüger
c78ac2e779 Merge luamml-pdf.sty into luamml.dtx 2021-06-26 20:23:14 +02:00
Marcel Fabian Krüger
903480a3f9 Prepare for better pdfTeX support 2021-06-26 19:49:28 +02:00
Marcel Fabian Krüger
f5acbf0ab4 Some mathord elements are operators 2021-06-26 05:15:58 +02:00
Marcel Fabian Krüger
cb5def2cb1 {smallmatrix} 2021-06-25 04:21:48 +02:00
Marcel Fabian Krüger
d4c6d493d2 Small fixes 2021-06-25 04:21:21 +02:00
Marcel Fabian Krüger
77b607e2e2 Generate nested <math> in <mtext> 2021-06-23 18:49:13 +02:00
Marcel Fabian Krüger
ccedfba57a Fix XML writer 2021-06-23 18:48:30 +02:00
Marcel Fabian Krüger
75ede91b5d Smaller improvements 2021-06-23 15:48:56 +02:00
Marcel Fabian Krüger
75c92b1ff5 Much better structelem support 2021-06-23 14:35:43 +02:00
Marcel Fabian Krüger
3a8dcdebe8 {multline} support (without labels on the left) 2021-06-23 11:05:45 +02:00
Marcel Fabian Krüger
4005fc172d gathered support 2021-06-23 04:39:58 +02:00
Marcel Fabian Krüger
36b3c596a7 Add {gather} support 2021-06-22 23:24:25 +02:00
Marcel Fabian Krüger
ffe3514b75 Use new flag_save variants 2021-06-22 18:10:02 +02:00
Marcel Fabian Krüger
3decacd033 Apply some protection 2021-06-22 18:10:02 +02:00
Marcel Fabian Krüger
09b8344549 luamml.sty is now generated 2021-06-22 18:10:02 +02:00
Marcel Fabian Krüger
8834b6af0d Start on documentation 2021-06-22 17:00:45 +02:00
Marcel Fabian Krüger
f6c7bb6b98 Don't break tabular when array is loaded 2021-06-22 16:59:38 +02:00
Marcel Fabian Krüger
9e501b2d76 Don't try to tag \eqno elements 2021-06-22 12:56:04 +02:00
Marcel Fabian Krüger
71b8d151d4 Avoid empty mtext before aligned 2021-06-18 23:15:12 +02:00
Marcel Fabian Krüger
fcfe981c9e Support aligned 2021-06-18 23:03:39 +02:00
Marcel Fabian Krüger
cd11aa478c Use official interfaces instead of internals 2021-06-17 13:29:32 +02:00
Marcel Fabian Krüger
6cb32caeb8 Use AF tagging in example 2021-06-17 13:29:16 +02:00
Marcel Fabian Krüger
d5c0f98b91 s/\.mml/\.xml/g 2021-06-16 17:02:47 +02:00
Marcel Fabian Krüger
fd9c8f4fcc Use luamml-demo for test_tex 2021-06-16 15:40:38 +02:00
Marcel Fabian Krüger
a673af336c Add luamml-demo package with reasonable defaults 2021-06-16 15:40:38 +02:00
Marcel Fabian Krüger
209f0cb13b Start using new flags 2021-06-16 15:40:38 +02:00
Marcel Fabian Krüger
702e974485 New flag values 2021-06-16 15:40:38 +02:00
Marcel Fabian Krüger
fba9e02b92 Support pdfMML annotations in LuaTeX 2021-06-16 15:21:19 +02:00
Marcel Fabian Krüger
1bda0c5892 Adapt to new pdfTeX patch version 2021-06-16 15:14:32 +02:00
Marcel Fabian Krüger
57f54f6ef6 Remove old table system 2021-06-06 17:06:40 +02:00
Marcel Fabian Krüger
dfee31b4b8 pdfmml \showstream support 2021-06-06 06:23:50 +02:00
Marcel Fabian Krüger
e0705c210b Add \RegisterFamilyMapping 2021-06-06 06:23:50 +02:00
Marcel Fabian Krüger
948818c8bc Generalize mn joining to family based joining 2021-06-04 13:25:05 +02:00
Marcel Fabian Krüger
5be2282e9b Implement pdfTeX auto node counting 2021-06-03 18:12:56 +02:00
Marcel Fabian Krüger
6da728c217 Make family mappings configurable in pdfTeX 2021-06-03 17:06:01 +02:00
Marcel Fabian Krüger
e7eb77a396 Offset in pdf mode should default to last node
Otherwise superscript/subscripts don't work correctly
2021-06-02 21:15:37 +02:00
Marcel Fabian Krüger
29bda8e8c2 Improve pdfmml.lua interface 2021-06-02 21:14:10 +02:00
Marcel Fabian Krüger
9d76be873e Update README 2021-06-01 23:36:41 +02:00
Marcel Fabian Krüger
06149a5e99 Annotate \models 2021-06-01 23:17:29 +02:00
Marcel Fabian Krüger
dd7098fb4a Add pdfmml sample 2021-06-01 23:00:21 +02:00
Marcel Fabian Krüger
d6b2747171 Support annotations while parsing pdfTeX log 2021-06-01 22:59:01 +02:00
Marcel Fabian Krüger
a782003db4 Allow properties to suppress nodes 2021-06-01 22:28:27 +02:00
Marcel Fabian Krüger
06915c6aa3 Mathlists (m)kern support 2021-05-31 13:25:08 +02:00
Marcel Fabian Krüger
a0dc11d37d Rename and rewrite pdfTeX stuff 2021-05-31 12:52:44 +02:00
Marcel Fabian Krüger
db60580191 Preserve core for user provided nodes 2021-05-31 01:54:21 +02:00
Marcel Fabian Krüger
a30b875e7a Support \phantom and friends 2021-05-31 00:26:35 +02:00
Marcel Fabian Krüger
80f95f575a Support sized delimiters 2021-05-30 20:37:03 +02:00
Marcel Fabian Krüger
87e67e0807 Fix typo 2021-05-30 03:48:01 +02:00
Marcel Fabian Krüger
ba4b30d49b Parse log file 2021-05-30 03:29:50 +02:00
Marcel Fabian Krüger
483ab6a572 Remaining noads 2021-05-29 13:34:38 +02:00
Marcel Fabian Krüger
7a2a93fe5a Working \showlists parsing experiment 2021-05-29 12:39:24 +02:00
Marcel Fabian Krüger
f982de18ee Baby steps towards pdfTeX comptibility 2021-05-28 18:25:25 +02:00
Marcel Fabian Krüger
207ea33a4e Fix wrong local 2021-05-27 14:45:31 +02:00
Marcel Fabian Krüger
1961c2445c Forgot interction of metatable 2021-05-27 07:35:12 +02:00
Marcel Fabian Krüger
c9904b3bc5 Add support for mathml_filter 2021-05-27 05:03:07 +02:00
Marcel Fabian Krüger
ebc813d299 Prepare support for preexisting structure elements 2021-05-27 03:15:24 +02:00
Marcel Fabian Krüger
ec8fd3c1a8 Only mark symbols belonging to MML token elements 2021-05-24 18:41:12 +02:00
Marcel Fabian Krüger
6f92838e19 Small improvements/fixes 2021-05-21 17:30:23 +02:00
Marcel Fabian Krüger
913b7150e1 Enable indentation / newlines in MathML writer 2021-05-18 21:01:48 +02:00
Marcel Fabian Krüger
1046e096ed Fix missing filename case 2021-05-18 20:56:47 +02:00
Marcel Fabian Krüger
2974676fab Add interface for writing MathML into a file 2021-05-18 19:39:30 +02:00
Marcel Fabian Krüger
c4c2b701b5 First attempt at generating structure elements 2021-05-12 07:43:57 +02:00
Marcel Fabian Krüger
98fd0a477a Enforce TeX like spacing 2021-05-13 02:56:37 +02:00
Marcel Fabian Krüger
ba219a05bb Suppress automatic spacing 2021-05-11 06:18:51 +02:00
Marcel Fabian Krüger
562f9b1248 Don't use strings for nod subtyps 2021-05-11 06:16:36 +02:00
Marcel Fabian Krüger
f9a23ca0b4 Simpler mathbin correction 2021-05-07 23:14:20 +02:00
44 changed files with 5856 additions and 359 deletions

38
.github/actions/ctan-upload/action.yaml vendored Normal file
View File

@ -0,0 +1,38 @@
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 Normal file
View File

@ -0,0 +1,59 @@
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

78
.github/workflows/deploy.yaml vendored Normal file
View File

@ -0,0 +1,78 @@
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

53
.github/workflows/main.yaml vendored Normal file
View File

@ -0,0 +1,53 @@
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 Normal file
View File

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

View File

@ -1,14 +1,14 @@
# 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.**
# LuaMML: Automated LuaLaTeX math to MathML conversion
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.
## Demo
Run `lualatex test_tex` to see all equations from [our example file](./test_tex.tex) converted into MathML.
## 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.
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).
<!-- 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. -->

View File

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

17
config-lua.lua Normal file
View File

@ -0,0 +1,17 @@
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'}

33
config-pdf.lua Normal file
View File

@ -0,0 +1,33 @@
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,10 +4,28 @@ 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()
@ -15,24 +33,81 @@ lua.get_functions_table()[funcid] = function()
-- local box = token.scan_int()
local boxnum = 0
local startmath = tex.box[boxnum].list
assert(startmath.id == node.id"math")
store_column(startmath, true)
assert(startmath.id == math_t)
store_column(startmath)
end
funcid = luatexbase.new_luafunction'__luamml_amsmath_finalize_table:'
token.set_lua('__luamml_amsmath_finalize_table:', funcid)
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 = string.rep('right left', columns, ' ')
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)
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
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 = table.concat(spacing, ' ', 2, #spacing-2)
mml_table.columnspacing = #spacing > 3 and table.concat(spacing, ' ', 2, #spacing-2) or nil
save_result(mml_table, true)
end

View File

@ -11,7 +11,86 @@ 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 noad_sub = node.subtypes'noad'
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 radical_sub = node.subtypes'radical'
local fence_sub = node.subtypes'fence'
@ -25,8 +104,13 @@ local digit_map = {["0"] = true, ["1"] = true,
["5"] = true, ["6"] = true, ["7"] = true,
["8"] = true, ["9"] = true,}
-- 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 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 = {}
local nodes_to_table
@ -35,47 +119,71 @@ 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/user_provided
-- or space_like
-- 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] props = props and props.mathml_table
if props then return props 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 char = delim.small_char
if char == 0 then
return {[0] = 'mspace', width = string.format("%.3fpt", tex.nulldelimiterspace/65781.76)}, space_like
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
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 }
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
end
end
-- Like kernel_to_table but always a math_char_t. Also creating a mo and potentially remapping to handle combining chars
-- 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.
local function acc_to_table(acc, cur_style, stretch)
if not acc then return end
local props = properties[acc] props = props and props.mathml_table
if props then return props 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
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
return {[0] = 'mo', char, ['tex:family'] = fam ~= 0 and fam or nil, stretchy = stretch}
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
end
local function kernel_to_table(kernel, cur_style)
local function kernel_to_table(kernel, cur_style, text_families)
if not kernel then return end
local props = properties[kernel] props = props and props.mathml_table
if props then return props, user_provided 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 id = kernel.id
if id == math_char_t then
local fam = kernel.fam
@ -84,27 +192,40 @@ local function kernel_to_table(kernel, cur_style)
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
mathvariant = utf8.len(char) == 1 and elem == 'mi' and utf8.codepoint(char) < 0x10000 and 'normal' or nil,
[':nodes'] = {kernel},
}
return result, result
elseif id == sub_box_t then
if kernel.list.id == hlist_t then -- We directly give up for vlists
local result = to_text(kernel.list.head)
return result, result
if mathml_filter then
return mathml_filter(result, result)
else
return result, result
end
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)
else
local result = {[0] = 'mi', {[0] = 'mglyph', ['tex:box'] = kernel.list}}
return result, result
end
elseif id == sub_mlist_t then
return nodes_to_table(kernel.list, cur_style)
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
else
error'confusion'
end
end
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))
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)
if sub then
if sup then
return {[0] = 'msubsup', t, sub, sup}, core
@ -125,7 +246,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 noad_sub[after.subtype] ~= 'ord' then return end
if after.subtype ~= noad_ord then return end
after = after.nucleus
if not after then return end
if after.id ~= math_char_t then return end
@ -133,70 +254,91 @@ local function maybe_to_mn(noad, core)
core[0] = 'mn'
end
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
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
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 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
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
return nucleus, mn, false
joining[':nodes'] = cnodes
end
end
nucleus = do_sub_sup(joining, joining, noad, cur_style, text_families)
if nucleus == joining then
return nil, joining, joining
else
return nucleus, joining, false
end
elseif not noad.sub and not noad.sup then
return core, core, core
end
end
end
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
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
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
if core.mathvariant == 'normal' then core.mathvariant = nil end
end
nucleus['tex:class'] = class
if core.mathvariant == 'normal' then core.mathvariant = nil end
core.lspace, core.rspace = 0, 0
end
nucleus['tex:class'] = noad_names[sub]
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))
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)
return {[0] = sup and (sub and 'munderover' or 'mover') or 'munder',
nucleus,
sub or sup,
sub and sup,
}, core
end
elseif class == 'under' then
elseif sub == noad_under then
return {[0] = 'munder',
nucleus,
{[0] = 'mo', '_',},
}, core
elseif class == 'over' then
elseif sub == noad_over then
return {[0] = 'mover',
nucleus,
{[0] = 'mo', '\u{203E}',},
}, core
elseif class == 'vcenter' then -- Ignored. Nucleus will need special handling anyway
elseif sub == noad_vcenter then -- Ignored. Nucleus will need special handling anyway
else
error[[confusion]]
end
return do_sub_sup(nucleus, core, noad, cur_style)
return do_sub_sup(nucleus, core, noad, cur_style, text_families)
end
local function accent_to_table(accent, sub, cur_style)
local nucleus, core = kernel_to_table(accent.nucleus, cur_style//2*2+1)
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 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',
@ -218,17 +360,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)
local function radical_to_table(radical, sub, cur_style, text_families)
local kind = radical_sub[sub]
local nucleus, core = kernel_to_table(radical.nucleus, cur_style//2*2+1)
local nucleus, core = kernel_to_table(radical.nucleus, cur_style//2*2+1, text_families)
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)}, nil
elem, core = {[0] = 'msqrt', nucleus, kernel_to_table(radical.degree, 7, text_families)}, nil
elseif kind == 'uunderdelimiter' then
elem, core = {[0] = 'munder', left, nucleus}, left
elseif kind == 'uoverdelimiter' then
@ -240,12 +382,12 @@ local function radical_to_table(radical, sub, cur_style)
else
error[[confusion]]
end
return do_sub_sup(elem, core, radical, cur_style)
return do_sub_sup(elem, core, radical, cur_style, text_families)
end
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 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 left = delim_to_table(fraction.left)
local right = delim_to_table(fraction.right)
local mfrac = {[0] = 'mfrac',
@ -272,8 +414,31 @@ end
local function fence_to_table(fence, sub, cur_style)
local delim, core = delim_to_table(fence.delim)
if delim[0] == 'mo' then
delim.fence = 'true'
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'
end
return delim, core
end
@ -296,67 +461,87 @@ local function rule_to_table(rule, sub, cur_style)
if height == running_length then
height = '0.8em'
else
height = height
height = string.format("%.3fpt", height/65781.76)
end
local depth = rule.depth
if depth == running_length then
depth = '0.2em'
else
depth = depth
depth = string.format("%.3fpt", depth/65781.76)
end
return {[0] = 'mspace', mathbackground = 'currentColor', width = width, height = height, depth = depth}, space_like
end
function nodes_to_table(head, cur_style)
-- 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)
local t = {[0] = 'mrow'}
local result = t
local nonscript
local core, mn = space_like
local no_binop_context, last_noad, last_noad_core = true
local core, last_noad, last_core, joining = space_like, nil, nil, nil
for n, id, sub in node.traverse(head) do
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 new_core, new_joining, new_node, new_noad
local props = properties[n]
props = props and props.mathml_table
if props then
t[#t+1], new_core = props, user_provided
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
elseif id == noad_t then
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
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
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
new_node = new_n -- might be nil
end
new_noad = sub
elseif id == accent_t then
t[#t+1], new_core = accent_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
new_node, new_core = accent_to_table(n, sub, cur_style, text_families)
new_noad = noad_ord
elseif id == style_t then
if sub ~= cur_style then
if #t == 0 then
@ -376,52 +561,77 @@ function nodes_to_table(head, cur_style)
new_core = space_like
elseif id == choice_t then
local size = cur_style//2
t[#t+1], new_core = nodes_to_table(n[size == 0 and 'display'
new_node, 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), space_like
or assert(false)], 2*size, text_families), space_like
elseif id == radical_t then
t[#t+1], new_core = radical_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
new_node, new_core = radical_to_table(n, sub, cur_style, text_families)
new_noad = noad_ord
elseif id == fraction_t then
t[#t+1], new_core = fraction_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
new_node, new_core = fraction_to_table(n, sub, cur_style, text_families)
new_noad = noad_inner
elseif id == fence_t then
t[#t+1], new_core = fence_to_table(n, sub, cur_style)
no_binop_context, last_noad, last_noad_core = false, nil, nil
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
elseif id == kern_t then
if not nonscript then
t[#t+1], new_core = space_to_table(n.kern, sub, cur_style)
new_node, 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
t[#t+1], new_core = space_to_table(n.width, sub, cur_style)
new_node, new_core = space_to_table(n.width, sub, cur_style)
end
end
elseif id == rule_t then
t[#t+1], new_core = rule_to_table(n, sub, cur_style)
new_node, 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
mn = new_mn
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
-- In TeX, groups are never space like
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
end
-- In TeX, groups are never space like, so we insert an artificial node instead.
-- This node should be ignored for most purposes
if core == space_like then
core = {[0] = 'mi', intent = '@ignore'}
core = {[0] = 'mi', ['tex:ignore'] = 'true'}
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
end
local function register_remap(family, mapping)
@ -447,6 +657,6 @@ end
return {
register_family = register_remap,
process = function(head, style) return nodes_to_table(head, style or 2) end,
process = function(head, style, families) return nodes_to_table(head, style or 2, families) end,
make_root = to_math,
}

82
luamml-demo.sty Normal file
View File

@ -0,0 +1,82 @@
\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,6 +26,7 @@ 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,
@ -34,7 +35,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, 0x22A9, 0x22A4, 0x22A5,
0x2200, 0x2203, 0xAC, 0x2205, 0x211C, 0x2111, 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] = 'mrow'}, {}, 0
local result, subresult, i = {[0] = 'mtext'}, {}, 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
subresult[i] = glyph_info
result[i] = glyph_info
else
local char, fid = node.is_glyph(n)
if char then
@ -23,27 +23,23 @@ local function to_unicode(head, tail)
i = i+1
if uni then
if type(uni) == 'number' then
subresult[i] = utf.char(uni)
result[i] = utf.char(uni)
else
subresult[i] = utf.char(table.unpack(uni))
result[i] = utf.char(table.unpack(uni))
end
else
if char < 0x110000 then
subresult[i] = utf.char(char)
result[i] = utf.char(char)
else
subresult[i] = '\u{FFFD}'
result[i] = '\u{FFFD}'
end
end
elseif node.id'math' == id then
if props then
local mml = props.saved_mathml_table
local mml = props.saved_mathml_table or props.saved_mathml_core
if mml then
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
i = i+1
result[i] = mml
n = node.end_of_math(n)
end
end
@ -52,46 +48,23 @@ 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
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
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
end
elseif node.id'hlist' == id then
local nested = to_unicode(n.head)
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
table.move(nested, 1, #nested, i+1, result)
i = i+#nested
elseif node.id'vlist' == id then
i = i+1
subresult[i] = '\u{FFFD}'
result[i] = '\u{FFFD}'
elseif node.id'rule' == id then
if n.width ~= 0 then
i = i+1
subresult[i] = '\u{FFFD}'
result[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,8 +1,172 @@
\ProvidesExplPackage {luamml-patches-amsmath} {2021-04-23} {0.0.1-alpha}
\ProvidesExplPackage {luamml-patches-amsmath} {2024-10-30} {0.2.0}
{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
@ -11,13 +175,12 @@
\@lign
$
\m@th
\displaystyle {
##
}
\displaystyle
{##}
\ifmeasuring@
\luamml_flag_ignore:
\luamml_ignore:
\else
\luamml_flag_save:
\luamml_save:nNn {} \displaystyle {mtd}
\fi
$
}
@ -39,9 +202,9 @@
##
}
\ifmeasuring@
\luamml_flag_ignore:
\luamml_ignore:
\else
\luamml_flag_save:
\luamml_save:nNn {} \displaystyle {mtd}
\fi
$
}
@ -95,15 +258,160 @@
\cs_set:Npn \endalign {
\math@cr
\black@ \totwidth@
\__luamml_amsmath_finalize_table:
\__luamml_amsmath_finalize_table:n {align}
\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

@ -0,0 +1,37 @@
\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} {2021-04-23} {0.0.1-alpha}
\ProvidesExplPackage {luamml-patches-array} {2024-10-30} {0.2.0}
{Feel free to add a description here}
\lua_now:n { require'luamml-array' }
@ -12,47 +12,62 @@
\hfil
\hskip 1sp
\d@llarbegin
\cs_if_eq:NNTF \d@llarbegin \begingroup {
\insert@column
\d@llarend
} {
\__luamml_array_init_col:
\insert@column
\luamml_flag_save:
\luamml_save:nn {} {mtd}
\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_flag_save:
\luamml_save:nn {} {mtd}
\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_flag_save:
\luamml_save:nn {} {mtd}
\d@llarend
\__luamml_array_finalize_col:w 2~
}
\do@row@strut
\or
\setbox \ar@mcellbox \vbox \@startpbox { \@nextchar }
\insert@column
\insert@pcolumn
\@endpbox
\ar@align@mcell
\do@row@strut
\or
\vtop \@startpbox { \@nextchar }
\insert@column
\insert@pcolumn
\@endpbox
\do@row@strut
\or
\vbox \@startpbox { \@nextchar }
\insert@column
\insert@pcolumn
\@endpbox
\do@row@strut
\fi
@ -60,12 +75,27 @@
\prepnext@tok
}
\cs_set:Npn \endarray {
\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
\__luamml_array_finalize_array:
\mode_if_math:T { \__luamml_array_finalize_array: }
\@arrayright
\gdef \@preamble {}
}
}

76
luamml-patches-kernel.sty Normal file
View File

@ -0,0 +1,76 @@
\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

@ -0,0 +1,20 @@
\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

@ -0,0 +1,35 @@
\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
}

50
luamml-pdf-demo.sty Normal file
View File

@ -0,0 +1,50 @@
\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:

123
luamml-structelemwriter.lua Normal file
View File

@ -0,0 +1,123 @@
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,8 +4,23 @@ 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 = tex.nest[tex.nest.ptr-1].head
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 props = properties[row_temp]
if not props then
props = {}
@ -20,6 +35,7 @@ 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
@ -28,24 +44,30 @@ local function store_column_xml(mml, display)
end
mml = {[0] = 'mtd', mml}
end
end
table.insert(store_get_row(), mml)
return mml
end
local function store_column(startmath, display)
local function store_column(startmath)
local props = properties[startmath]
if not props then return end
local mml = props.saved_mathml_table
if mml then return store_column_xml(mml, display) end
local mml = props.saved_mathml_table or props.saved_mathml_core
if mml then return store_column_xml(mml) end
end
local function store_tag(xml)
local mml_row = store_get_row()
mml_row[0] = 'mlabeledtr'
xml.intent = ':equationlabel'
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
@ -83,5 +105,6 @@ 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,
}

128
luamml-tex-annotate.lua Normal file
View File

@ -0,0 +1,128 @@
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,8 +5,43 @@ 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')
@ -15,70 +50,128 @@ 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
-- Possible flag values:
-- 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
--
-- 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 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 mlist_buffer
local mlist_result, mlist_display
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')
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
--
-- 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.
local out_file
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
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)
local flag = tex.count.l__luamml_flag_int
if flag & 3 == 3 then
if flag & 4 == 4 then
assert(mlist_buffer == nil)
mlist_buffer = node.copy_list(mlist)
end
if tex.nest.top.mode == mmode then -- This is a equation label generated with \eqno
return true
end
local new_mlist, buffer_tail
local flag = tex.count.l__luamml_flag_int
if flag & 3 == 0 then
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
if flag & 4 == 4 then
new_mlist, buffer_tail = assert(mlist_buffer), node.tail(mlist_buffer)
mlist.prev, buffer_tail.next = buffer_tail, mlist
mlist_buffer = nil
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
new_mlist = mlist
xml = {[0] = element_type, xml}
end
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
end
if not display and flag & 1 == 1 then
local props = properties[startmath]
if not props then
props = {}
properties[startmath] = props
end
props.saved_mathml_table = xml
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
if buffer_tail then
mlist.prev, buffer_tail.next = nil, nil
node.flush_list(new_mlist)
end
return true
end, 'dump_list')
@ -91,7 +184,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(make_root(mlist_result, mlist_display and 0 or 2))
local mml = write_xml(mlist_result, tex.count.l__luamml_pretty_int & 8 == 8)
if tex.count.tracingmathml == 1 then
texio.write_nl(mml .. '\n')
end
@ -99,6 +192,41 @@ 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,7 +10,9 @@ local escapes = {
['&'] = "&amp;",
}
local function escape_text(text)
return string.gsub(tostring(text), '("<>&)', escapes)
return string.gsub(string.gsub(tostring(text), '["<>&]', escapes), '[\x00-\x08\x0B\x0C\x0E-\x1F]', function(x)
return string.format('^^%02x', string.byte(x))
end)
end
local attrs = {}
@ -19,8 +21,11 @@ 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
end end
table.sort(attrs)
local out = string.format('%s<%s%s', indent or '', escaped_name, table.concat(attrs))
@ -30,14 +35,17 @@ 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 then
if inner_indent and not is_string 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
@ -46,5 +54,5 @@ end
return function(element, indent, version)
return (version == '11' and '<?xml version="1.1"?>' or '') ..
write_elem(element, indent and '' or nil)
write_elem(element, indent and '\n' or nil)
end

552
luamml.dtx Normal file
View File

@ -0,0 +1,552 @@
% \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

View File

@ -1,32 +0,0 @@
\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}

39
pdfmml-emulate-node.lua Normal file
View File

@ -0,0 +1,39 @@
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

61
pdfmml-logreader.lua Normal file
View File

@ -0,0 +1,61 @@
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

234
pdfmml-showlists.lua Normal file
View File

@ -0,0 +1,234 @@
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

96
pdfmml.lua Executable file
View File

@ -0,0 +1,96 @@
#!/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

@ -0,0 +1,98 @@
% \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.

View File

@ -1,46 +0,0 @@
\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}

145
testfiles-lua/cases.mlr Normal file
View File

@ -0,0 +1,145 @@
<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>

39
testfiles-lua/cases.mlt Normal file
View File

@ -0,0 +1,39 @@
\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

@ -0,0 +1,59 @@
\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

237
testfiles-lua/test_xml.mlr Normal file
View File

@ -0,0 +1,237 @@
<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

@ -0,0 +1,49 @@
\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}

51
testfiles-pdf/test.mlr Normal file
View File

@ -0,0 +1,51 @@
<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>

20
testfiles-pdf/test.mlt Normal file
View File

@ -0,0 +1,20 @@
\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}

218
testfiles-pdf/test_pdf.txr Normal file
View File

@ -0,0 +1,218 @@
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

@ -0,0 +1,20 @@
\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}