diff --git a/.github/actions/ctan-upload/action.yaml b/.github/actions/ctan-upload/action.yaml new file mode 100644 index 0000000..3202baf --- /dev/null +++ b/.github/actions/ctan-upload/action.yaml @@ -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: luamml + version: ${{ inputs.version }} + author: 'Marcel Krüger, LaTeX Project Team' + uploader: ${{ inputs.uploader }} + email: ${{ inputs.email }} + license: lppl1.3c + 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. diff --git a/.github/tl_packages b/.github/tl_packages new file mode 100644 index 0000000..ea7ca2b --- /dev/null +++ b/.github/tl_packages @@ -0,0 +1,60 @@ +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 +l3experimental +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 diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..42f7d84 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -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/luamml-ctan.zip" + dry-run: true + + github: + runs-on: ubuntu-latest + 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/luamml-ctan.zip" + dry-run: false diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..16d3751 --- /dev/null +++ b/.github/workflows/main.yaml @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..19c2c43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/build/ +*.aux +*.log +*.pdf +*.xml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a8ada0e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog +All notable changes to the `luamml` package since the +2025-02-17 will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +this project uses date-based 'snapshot' version identifiers. + +## [Unreleased] + + - Ulrike Fischer, 2025-03-06 + * add class attribute to math environments + * correct columnalign (take label column into account) + * add intent :continued-row in split enviroment + * add intent :system-of-equations to environments + * temporary patch to \common@align@ending to store the environment name + * start some debugging functions (variable debugmtable) + * correct columnspacing + * add intent :pause-medium between columns + +## 2025-02-21 + +- Ulrike Fischer, 2025-02-21 + * change intent :equationlabel to :equation-label and + :noequationlabel to :no-equation-label + + +## 2025-02-17 + +### Changed +- Ulrike Fischer, 2025-02-17 + * moved all patches into latex-lab + * added sockets to luamml.dtx + * changed handling of tags/labels: empty tags produces a row too and have an intent + * corrected small bugs + +- Ulrike Fischer, 2024-11-29 + luamml-structelemwriter.lua: moved the actualtext for e.g. stretched braces from the structure element to the mc-chunk. + +- Ulrike Fischer, 2024-03-03 + luamml.dtx: add plug for mbox socket to correctly annotate them in math. + +- Ulrike Fischer, 2024-11-29 + luamml-structelemwriter.lua: use structnum instead of label when stashing. diff --git a/README.md b/README.md index 254df05..f574142 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,17 @@ -# 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). +## License +LuaMML may be modified and distributed under the terms of the [LaTeX Project Public License](https://www.latex-project.org/lppl/), version 1.3c or greater. +It is written by Marcel Krüger and the LaTeX Project Team. + + + diff --git a/build.lua b/build.lua index 82ac3ca..553d44d 100644 --- a/build.lua +++ b/build.lua @@ -1,7 +1,15 @@ module = "luamml" -tdsroot = "lualatex" +tdsroot = "lualatex" installfiles = { "luamml-*.lua", "*.sty" } -stdengine = "luatex" -checkengines = {"luatex"} -sourcefiles = installfiles +sourcefiles = { "luamml-*.lua", "*.sty", "*.dtx" } +typesetsuppfiles = { "*.tex" } +typesetsourcefiles = { "*.tex" } +stdengine = "luatex" +unpackfiles = { "*.dtx" } +typesetexe = "lualatex" + +checkconfigs = { + 'config-lua', + 'config-pdf', +} diff --git a/config-lua.lua b/config-lua.lua new file mode 100644 index 0000000..22e287b --- /dev/null +++ b/config-lua.lua @@ -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'} diff --git a/config-pdf.lua b/config-pdf.lua new file mode 100644 index 0000000..1fc73e1 --- /dev/null +++ b/config-pdf.lua @@ -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'} diff --git a/luamml-amsmath.lua b/luamml-amsmath.lua index eacb91f..48732f3 100644 --- a/luamml-amsmath.lua +++ b/luamml-amsmath.lua @@ -3,11 +3,30 @@ local make_root = require'luamml-convert'.make_root 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 store_notag = require'luamml-table'.store_notag 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 +34,139 @@ 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 + +-- This function is used to add a intent :continued-row to +-- rows of a split environment. +-- we assume that the table is a mtable with mrow with mtd. +-- we check row 2..n. If the first cell has only one element and +-- for this element 'tex:ignore' has been set, we assume a continued row and +-- set the intent on the mrow. +local function add_intent_continued_row (table) + for index,rowtable in ipairs(table) do + if table[index][1] and table[index][1][1] then -- just for safety ... + if index > 1 and #table[index][1]==1 and table[index][1][1]['tex:ignore'] then + table[index]['intent']=':continued-row' + end + end + end +end + +-- This function add an intent =":pause-medium" on every second mtd in a table +-- currently it is also on the first (after the label) but this could be changed +-- used in __luamml_amsmath_finalize_table:n for +-- 'align' or 'alignat' or 'flalign' or 'xalignat' or 'xxalignat' +local function add_intent_pause (mmltable) + for mtrindex,mtrtable in ipairs(mmltable) do + for mtdindex,mtdtable in ipairs(mtrtable) do + if (mtdindex % 2 == 0) then + mtdtable['intent']=':pause-medium' + end + end + end +end + + +-- debug function for tables +-- activate with \directlua{debugmtable=2} or \directlua{debugmtable='split'} +local function debug_mtable (mtable,kind) + if debugmtable and (debugmtable==2) or (debugmtable==kind) then + texio.write_nl('==============') + texio.write_nl(kind) + texio.write_nl(table.serialize(mtable)) + texio.write_nl('==============') + end +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() + kind = kind:gsub("*","") + local mml_table = get_table() + if not mml_table then return end + mml_table.displaystyle = true + mml_table.class=kind + if kind=="split" then + add_intent_continued_row (mml_table) + end + local columns = node.count(node.id'align_record', tex.lists.align_head)//2 + mml_table.columnalign = kind == 'gathered' and 'center' or string.rep('right left', columns, ' ') + local spacing = {} + for n in node.traverse_id(node.id'glue', tex.lists.align_head) do + spacing[#spacing+1] = n.width == 0 and '0' or string.format('%.3fpt', n.width/65781.76) + end + mml_table.columnspacing = #spacing > 3 and table.concat(spacing, ' ', 2, #spacing-2) or nil + debug_mtable(mml_table,kind) + 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.class='smallmatrix' + 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} + debug_mtable(mml_table,kind) + 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() + kind = kind:gsub("*","") local mml_table = get_table() if not mml_table then return end mml_table.displaystyle = true + mml_table.class=kind + -- this should perhaps be configurable and extendable + if kind == 'align' or 'alignat' or 'flalign' or 'xalignat' or 'xxalignat' then + mml_table.intent=":system-of-equations" + add_intent_pause (mml_table) + end 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 == 'align' and 'left '..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 "0 "..table.concat(spacing, ' ', 2, #spacing-2) or nil + debug_mtable(mml_table,kind) save_result(mml_table, true) end @@ -50,9 +184,9 @@ funcid = luatexbase.new_luafunction'__luamml_amsmath_set_tag:' token.set_lua('__luamml_amsmath_set_tag:', funcid, 'protected') lua.get_functions_table()[funcid] = function() if not last_tag then - texio.write_nl'WARNING: Tag extraction failed' - return + store_notag({[0] = 'mtd',''}) + else + store_tag({[0] = 'mtd', last_tag}) + last_tag = nil end - store_tag({[0] = 'mtd', last_tag}) - last_tag = nil end diff --git a/luamml-array.lua b/luamml-array.lua index 9a7b358..f1fd7c5 100644 --- a/luamml-array.lua +++ b/luamml-array.lua @@ -54,7 +54,7 @@ end local saved_array -funcid = luatexbase.new_luafunction'__luamml_array_finalize_array:' +funcid = luatexbase.new_luafunction'__luamml_array_save_array:' token.set_lua('__luamml_array_save_array:', funcid) lua.get_functions_table()[funcid] = function() -- TODO: Error handling etc. diff --git a/luamml-convert.lua b/luamml-convert.lua index 3518422..abb6615 100644 --- a/luamml-convert.lua +++ b/luamml-convert.lua @@ -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 ) 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 } - return result, result + 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 + 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, mn, false + 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 stretchy[core[1]] then core.stretchy = false end + if not core.minsize then + if stretchy[core[1]] then core.stretchy = false end + end if core.mathvariant == 'normal' then core.mathvariant = nil end + core.lspace, core.rspace = 0, 0 end - nucleus['tex:class'] = class + 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,18 @@ 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 + -- UF 2024-12-04: force use of only one return value + elem, core = {[0] = 'mroot', 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 +383,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 +415,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 +462,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 +562,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 + 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 + -- 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 - return result, core + 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 +658,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, } diff --git a/luamml-demo.sty b/luamml-demo.sty new file mode 100644 index 0000000..55d938a --- /dev/null +++ b/luamml-demo.sty @@ -0,0 +1,82 @@ +\NeedsTeXFormat{LaTeX2e} +\ProvidesExplPackage{luamml-demo}{2025-03-06}{0.5.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: diff --git a/luamml-legacy-mappings.lua b/luamml-legacy-mappings.lua index f4236ce..6a56b70 100644 --- a/luamml-legacy-mappings.lua +++ b/luamml-legacy-mappings.lua @@ -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, diff --git a/luamml-lr.lua b/luamml-lr.lua index b1cb359..b78a7c3 100644 --- a/luamml-lr.lua +++ b/luamml-lr.lua @@ -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 diff --git a/luamml-patches-amsmath.sty b/luamml-patches-amsmath.sty index 421f47d..5ebf157 100644 --- a/luamml-patches-amsmath.sty +++ b/luamml-patches-amsmath.sty @@ -1,109 +1,440 @@ -\ProvidesExplPackage {luamml-patches-amsmath} {2021-04-23} {0.0.1-alpha} +\ProvidesExplPackage {luamml-patches-amsmath} {2025-03-06} {0.5.0} {Feel free to add a description here} \lua_now:n { require'luamml-amsmath' } -\cs_set:Npn \align@preamble { - & - \hfil - \strut@ - \setboxz@h { - \@lign - $ - \m@th - \displaystyle { - ## - } - \ifmeasuring@ - \luamml_flag_ignore: - \else - \luamml_flag_save: - \fi - $ - } - \ifmeasuring@ - \savefieldlength@ - \else - \__luamml_amsmath_add_box_to_row: - \fi - \set@field - \tabskip\z@skip - & - \setboxz@h { - \@lign - $ - \m@th - \displaystyle - { - {} - ## - } - \ifmeasuring@ - \luamml_flag_ignore: - \else - \luamml_flag_save: - \fi - $ - } - \ifmeasuring@ - \savefieldlength@ - \else - \__luamml_amsmath_add_box_to_row: - \fi - \set@field - \hfil - \tabskip\alignsep@ -} - -\cs_set:Npn \math@cr@@@align { - \ifst@rred - \nonumber - \fi - \if@eqnsw - \global \tag@true - \fi - \global \advance \row@ \@ne - \add@amps \maxfields@ - \omit - \kern -\alignsep@ - \iftag@ - \setboxz@h { - \@lign - \strut@ - { \make@display@tag } - } - \place@tag - \__luamml_amsmath_set_tag: - \fi - \ifst@rred - \else - \global \@eqnswtrue - \fi - \global \lineht@ \z@ - \cr -} - -\cs_set:Npn \maketag@@@ #1 { - \hbox { - \m@th - \normalfont - #1 - \__luamml_amsmath_save_tag: - } -} - -\cs_set:Npn \endalign { - \math@cr - \black@ \totwidth@ - \__luamml_amsmath_finalize_table: +% 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. +\IfPackageAtLeastTF{latex-lab-testphase-math}{2025-02-24} + {} + { + \def\common@align@ending { + \math@cr \black@\totwidth@ + \UseExpandableTaggingSocket {math/luamml/mtable/finalize} {\@currenvir} \egroup \ifingather@ \restorealignstate@ \egroup \nonumber - \ifnum0=‘{\fi\iffalse}\fi + \ifnum0=`{\fi\iffalse}\fi \else - $$ + $$% \fi \ignorespacesafterend -} + } + } + +\IfPackageAtLeastTF{latex-lab-testphase-math}{2025-01-24} + {} + { + \PackageInfo{luamml}{patching~\string\start@aligned} + % aligned and friends + \cs_set:Npn \start@aligned #1#2 { + \RIfM@ + \else + \nonmatherr@ { \begin { \@currenvir } } + \fi + \savecolumn@ % Assumption: called inside a group + \UseTaggingSocket{ math/luamml/annotate/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 + {##} + \UseTaggingSocket{math/luamml/save/nNn}{ {} \displaystyle {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + \tabskip \z@skip + & \column@plus + $ + \m@th + \displaystyle + { + {} + ## + } + \UseTaggingSocket{math/luamml/save/nNn}{ {} \displaystyle {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + \hfil + \tabskip\alignsep@ + \crcr + \ams@return@opt@arg + } + \PackageInfo{luamml}{patching~gathered} + \renewcommand \gathered [1] [c] { + \RIfM@ + \else + \nonmatherr@ { \begin {gathered} } + \fi + \UseTaggingSocket{ math/luamml/annotate/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 + ## + \UseTaggingSocket{math/luamml/save/nNn}{ {} \displaystyle {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + \hfil + \crcr + \ams@return@opt@arg + } + \PackageInfo{luamml}{patching~\string\endaligned} + \cs_set:Npn \endaligned { + \crcr + \UseExpandableTaggingSocket{math/luamml/mtable/innertable/save} + \egroup + \restorecolumn@ + \egroup + \UseTaggingSocket{math/luamml/mtable/innertable/finalize} + } + \PackageInfo{luamml}{patching~\string\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 + {##} + \UseTaggingSocket{math/luamml/save/nNn}{ {} \displaystyle {mtd}} + $ + } + \UseTaggingSocket{math/luamml/mtable/finalizecol}{box} + \calc@shift@gather + \set@gather@field + \tabskip\@centering + & + \setboxz@h { + \strut@ + {##} + } + \dim_compare:nNnTF {0pt} = { + \box_wd:N \c_zero_int + } + { \place@tag@gather } + { + \place@tag@gather + \UseTaggingSocket{math/luamml/mtable/tag/set} + } + \tabskip \iftagsleft@ + \gdisplaywidth@ + \else + \z@skip + \span \fi + \crcr + #1 + } +% in latex lab, add the luamml_ignore to \measuring@true instead. + \PackageInfo{luamml}{patching~\string\gmeasure@} + \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} } + } + + + \PackageInfo{luamml}{patching~\string\endgather} + \cs_set:Npn \endgather { + \math@cr + \black@ \totwidth@ + \UseExpandableTaggingSocket{math/luamml/mtable/finalize} {gather} + \egroup + $$ + \ignorespacesafterend + } + + +% align and friends + \PackageInfo{luamml}{patching~\string\align@preamble} + \cs_set:Npn \align@preamble { + & + \hfil + \strut@ + \setboxz@h { + \@lign + $ + \m@th + \displaystyle + {##} + \ifmeasuring@ + \luamml_ignore: + \else + \UseTaggingSocket{math/luamml/save/nNn}{ {} \displaystyle {mtd}} + \fi + $ + } + \ifmeasuring@ + \savefieldlength@ + \else + \UseTaggingSocket{math/luamml/mtable/finalizecol}{box} + \fi + \set@field + \tabskip\z@skip + & + \setboxz@h { + \@lign + $ + \m@th + \displaystyle + { + {} + ## + } + \ifmeasuring@ + \luamml_ignore: + \else + \UseTaggingSocket{math/luamml/save/nNn}{ {} \displaystyle {mtd}} + \fi + $ + } + \ifmeasuring@ + \savefieldlength@ + \else + \UseTaggingSocket{math/luamml/mtable/finalizecol}{box} + \fi + \set@field + \hfil + \tabskip\alignsep@ + } + \PackageInfo{luamml}{patching~\string\math@cr@@@align} + \cs_set:Npn \math@cr@@@align { + \ifst@rred + \nonumber + \fi + \if@eqnsw + \global \tag@true + \fi + \global \advance \row@ \@ne + \add@amps \maxfields@ + \omit + \kern -\alignsep@ + \iftag@ + \setboxz@h { + \@lign + \strut@ + { \make@display@tag } + } + \place@tag + \UseTaggingSocket{math/luamml/mtable/tag/set} + \fi + \ifst@rred + \else + \global \@eqnswtrue + \fi + \global \lineht@ \z@ + \cr + } + +% This was lost anyway, as the latex-lab code overwrites +% the definition again. + \PackageInfo{luamml}{patching~\string\maketag@@@} + \cs_set:Npn \maketag@@@ #1 + { + \hbox { + \m@th + \normalfont + #1 + \UseTaggingSocket{math/luamml/mtable/tag/save} + } + } + \PackageInfo{luamml}{patching~\string\endalign} +% this handled in latex-lab through \common@align@ending + \cs_set:Npn \endalign { + \math@cr + \black@ \totwidth@ + \UseTaggingSocket{math/luamml/mtable/finalize} {align} + \egroup + \ifingather@ + \restorealignstate@ + \egroup + \nonumber + \ifnum0=`{\fi\iffalse}\fi + \else + $$ + \fi + \ignorespacesafterend + } + + \PackageInfo{luamml}{patching~\string\multline@} + % 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 + \UseTaggingSocket{math/luamml/mtable/aligncol} {left} + \fi + #1 + \ifmeasuring@ \else + \UseTaggingSocket{math/luamml/mtable/aligncol} {right} + \fi + } + } + + %this is not move to latex-lab as the luamml_ignore is inserting with + % \measuringtrue + \PackageInfo{luamml}{patching~\string\mmeasure@} + \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. + \PackageInfo{luamml}{patching~\string\endmultline@math} + \cs_set:Npn \endmultline@math { + \UseTaggingSocket{math/luamml/save/nNn}{{} \displaystyle {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + } + \PackageInfo{luamml}{patching~\string\rendmultline@} + \cs_set:Npn \rendmultline@ { + \iftag@ + \UseTaggingSocket{math/luamml/save/nNn}{{} \displaystyle {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + \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 + \UseTaggingSocket{math/luamml/mtable/tag/set} + \else + \hskip \multlinegap + \fi + \hfilneg + \math@cr + \UseExpandableTaggingSocket {math/luamml/mtable/finalize} {multline} + \egroup + $$ + } + \PackageInfo{luamml}{patching~\string\lendmultline@} + \cs_set:Npn \lendmultline@ { + \hfilneg + \hskip\multlinegap + \math@cr + \UseExpandableTaggingSocket {math/luamml/mtable/finalize} {multline} + %\__luamml_amsmath_finalize_table:n {multline} + \egroup + $$ + } + + \PackageInfo{luamml}{patching~smallmatrix} + \renewenvironment {smallmatrix} { + \UseTaggingSocket{ math/luamml/annotate/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 + ## + % No \scriptsize here since we want to add the mstyle nodes + \UseTaggingSocket{math/luamml/save/nn}{ {} {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + \hfil + && + \thickspace + \hfil + $ + \m@th + \scriptstyle + ## + % No \scriptsize here since we want to add the mstyle nodes + \UseTaggingSocket{math/luamml/save/nn}{ {} {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + \hfil + \crcr + }{% + \crcr + \UseExpandableTaggingSocket{math/luamml/mtable/smallmatrix/save} + \egroup + \egroup + \UseTaggingSocket{math/luamml/mtable/innertable/finalize} + \UseTaggingSocket{math/luamml/annotate/false} {}{ \, } + } + + % {cases} is defined by the kernel, but we patch the overwritten version by amsmath. + \PackageInfo{luamml}{patching~\string\env@cases} + \cs_set:Npn \env@cases { + \let \@ifnextchar \new@ifnextchar + \left \lbrace + \def \arraystretch {1.2} + \array {@{}l@{\quad \luamml_ignore:}l@{}} + } + \PackageInfo{luamml}{patching~\string\bBigg@} + \cs_set:Npn \bBigg@ #1 #2 { + { + \ensuremath { + \Uvextensible height~#1 \big@size axis~exact~#2 + } + } + } +} %end package test diff --git a/luamml-patches-amstext.sty b/luamml-patches-amstext.sty new file mode 100644 index 0000000..4d9df9d --- /dev/null +++ b/luamml-patches-amstext.sty @@ -0,0 +1,29 @@ +\ProvidesExplPackage {luamml-patches-amstext} {2025-03-06} {0.5.0} + {patches of amstext commands} + +% This is the same definition as in latex-lab-amsmath. It can go with the +% 2025-06-01 release. +\IfPackageAtLeastTF{latex-lab-testphase-math}{2025-01-24} + {} + { + \PackageInfo{luamml}{patching~\string\text@} + \sys_if_engine_luatex:T + { + \def\text@#1{ + \tag_socket_use:nnn {math/luamml/hbox}{} + {{% + \ifcase\mathstyle + \hbox{{#1}}\or + \hbox{{#1}}\or + \hbox{{#1}}\or + \hbox{{#1}}\or + \hbox{{\let\f@size\sf@size\selectfont#1}}\or + \hbox{{\let\f@size\sf@size\selectfont#1}}\or + \hbox{{\let\f@size\ssf@size\selectfont#1}}\or + \hbox{{\let\f@size\ssf@size\selectfont#1}}\or + \ERROR + \fi + \check@mathfonts + }}} + } + } diff --git a/luamml-patches-array.sty b/luamml-patches-array.sty deleted file mode 100644 index 854416b..0000000 --- a/luamml-patches-array.sty +++ /dev/null @@ -1,71 +0,0 @@ -\ProvidesExplPackage {luamml-patches-array} {2021-04-23} {0.0.1-alpha} - {Feel free to add a description here} - -\lua_now:n { require'luamml-array' } - -\cs_set:Npn \@classz { - \@classx - \@tempcnta \count@ - \prepnext@tok - \@addtopreamble { - \ifcase \@chnum - \hfil - \hskip 1sp - \d@llarbegin - \__luamml_array_init_col: - \insert@column - \luamml_flag_save: - \d@llarend - \__luamml_array_finalize_col:w 0~ - \do@row@strut - \hfil - \or - \hskip 1sp - \d@llarbegin - \__luamml_array_init_col: - \insert@column - \luamml_flag_save: - \d@llarend - \__luamml_array_finalize_col:w 1~ - \do@row@strut - \hfil - \or - \hfil - \hskip 1sp - \d@llarbegin - \__luamml_array_init_col: - \insert@column - \luamml_flag_save: - \d@llarend - \__luamml_array_finalize_col:w 2~ - \do@row@strut - \or - \setbox \ar@mcellbox \vbox \@startpbox { \@nextchar } - \insert@column - \@endpbox - \ar@align@mcell - \do@row@strut - \or - \vtop \@startpbox { \@nextchar } - \insert@column - \@endpbox - \do@row@strut - \or - \vbox \@startpbox { \@nextchar } - \insert@column - \@endpbox - \do@row@strut - \fi - } - \prepnext@tok -} - -\cs_set:Npn \endarray { - \crcr - \__luamml_array_save_array: - \egroup - \egroup - \__luamml_array_finalize_array: - \@arrayright - \gdef \@preamble {} -} diff --git a/luamml-patches-kernel.sty b/luamml-patches-kernel.sty new file mode 100644 index 0000000..f75f363 --- /dev/null +++ b/luamml-patches-kernel.sty @@ -0,0 +1,84 @@ +\ProvidesExplPackage {luamml-patches-kernel} {2025-03-06} {0.5.0} + {Feel free to add a description here} + + +\IfPackageAtLeastTF{latex-lab-testphase-math}{2025-01-24} + {} + { + \PackageInfo{luamml}{patching~\string\mathsm@sh} + \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 + } + } + + \PackageInfo{luamml}{patching~\string\mathph@nt} + \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 + } + } + \IfFileLoadedT {latex-lab-math.ltx} { + \RequirePackage{luamml-patches-lab-math} + } + } + +% This is not moved to latex-lab for now. It doesn't work properly with structure elements +% active: the content is outside of the math. +\@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} +} diff --git a/luamml-patches-lab-math.sty b/luamml-patches-lab-math.sty new file mode 100644 index 0000000..e1283bb --- /dev/null +++ b/luamml-patches-lab-math.sty @@ -0,0 +1,27 @@ +\ProvidesExplPackage {luamml-patches-lab-math} {2025-03-06} {0.5.0} + {Feel free to add a description here} + +% This definition is identical to the one in latex-lab-math. +% The redefinition and the whole patch file can be removed in 2025-06-01 +\IfPackageAtLeastTF{latex-lab-testphase-math}{2025-01-24} + {} + { + \AddToHook{begindocument} { + \PackageInfo{luamml}{patching~\string\common@align@ending} + \cs_set:Npn \common@align@ending { + \math@cr + \black@ \totwidth@ + \UseExpandableTaggingSocket{math/luamml/mtable/finalize}{align} + \egroup + \ifingather@ + \restorealignstate@ + \egroup + \nonumber + \ifnum0=`{\fi\iffalse}\fi + \else + $$ + \fi + \ignorespacesafterend + } + } +} diff --git a/luamml-patches-mathtools.sty b/luamml-patches-mathtools.sty new file mode 100644 index 0000000..577cbcb --- /dev/null +++ b/luamml-patches-mathtools.sty @@ -0,0 +1,38 @@ +\ProvidesExplPackage {luamml-patches-mathtools} {2025-03-06} {0.5.0} + {Feel free to add a description here} +\IfPackageAtLeastTF{latex-lab-testphase-math}{2025-01-24} + {} + { + \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{}## + \UseTaggingSocket{math/luamml/save/nNn}{ {} \displaystyle {mtd}} + $ + \UseTaggingSocket{math/luamml/mtable/finalizecol}{last} + \hfil + \crcr + \hfilneg + #1 + } +} diff --git a/luamml-pdf-demo.sty b/luamml-pdf-demo.sty new file mode 100644 index 0000000..fe0a226 --- /dev/null +++ b/luamml-pdf-demo.sty @@ -0,0 +1,50 @@ +\NeedsTeXFormat{LaTeX2e} +\ProvidesExplPackage{luamml-pdf-demo}{2025-03-06}{0.5.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: diff --git a/luamml-structelemwriter.lua b/luamml-structelemwriter.lua new file mode 100644 index 0000000..53718ae --- /dev/null +++ b/luamml-structelemwriter.lua @@ -0,0 +1,143 @@ +local struct_begin = token.create'tag_struct_begin:n' +local struct_use = token.create'tag_struct_use:n' +local struct_use_num = token.create'tag_struct_use_num: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 catlatex = luatexbase.registernumber("catcodetable@latex") + +ltx = ltx or {} +ltx.__tag = ltx.__tag or {} +ltx.__tag.struct = ltx.__tag.struct or {} +ltx.__tag.struct.luamml = ltx.__tag.struct.luamml or {} +ltx.__tag.struct.luamml.labels = ltx.__tag.struct.luamml.labels or {} + +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(catlatex,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 tree[':structnum'] then + return tex.runtoks(function() + return tex.sprint(struct_use_num, '{', tree[':structnum'], '}') + 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 + tree[':structnum'] = get_ltx().tag.get_struct_num_next() + stash = ', 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 + tex.sprint'}' + for j = 1, i do attrs[j] = nil end + + if tree[':nodes'] then + local n = tree[':nodes'] + tex.runtoks(function() + if tree[':actual'] then + tex.sprint(mc_begin,'{tag=Span,actualtext=') + tex.cprint(12,tree[':actual']) + tex.sprint('}') + else + tex.sprint{mc_begin, string.format('{tag=%s}', tree[0])} + end + -- 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' and not elem['tex:ignore'] then + if elem['intent']==':equation-label' and ltx.__tag.struct.luamml.labels then + if #ltx.__tag.struct.luamml.labels > 0 then + -- print("CHECK LABEL STRUCTURE: ",table.serialize(elem), table.serialize(ltx.__tag.struct.luamml.labels)) + local num= table.remove(ltx.__tag.struct.luamml.labels,1) + elem[1][#elem+1]={[':structnum']= num} + end + end + write_elem(elem) + end + end + tex.runtoks(function() + tex.sprint(struct_end) + end) +end + +return function(element, stash) + return write_elem(element, stash) +end diff --git a/luamml-table.lua b/luamml-table.lua index f4ab238..75fd1f7 100644 --- a/luamml-table.lua +++ b/luamml-table.lua @@ -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,32 +35,45 @@ local function store_get_row() end local function store_column_xml(mml, display) - if display and mml[0] == 'mstyle' and mml.displaystyle == true then - mml[0], mml.displaystyle, mml.scriptlevel = 'mtd', nil, nil - else - if display and mml[0] ~= 'mstyle' then - mml = {[0] = 'mstyle', displaystyle = false, mml} + if mml[0] ~= 'mtd' then + if display and mml[0] == 'mstyle' and mml.displaystyle == true then + mml[0], mml.displaystyle, mml.scriptlevel = 'mtd', nil, nil + else + if display and mml[0] ~= 'mstyle' then + mml = {[0] = 'mstyle', displaystyle = false, mml} + end + mml = {[0] = 'mtd', mml} end - mml = {[0] = 'mtd', mml} 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 = ':equation-label' table.insert(mml_row, 1, xml) last_tag = nil end +local function store_notag(xml) + local mml_row = store_get_row() + xml.intent = ':no-equation-label' + table.insert(mml_row, 1, xml) +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 +111,7 @@ return { store_column = store_column, store_column_xml = store_column_xml, store_tag = store_tag, + store_notag = store_notag, + set_row_attribute = set_row_attribute, get_table = get_table, } diff --git a/luamml-tex-annotate.lua b/luamml-tex-annotate.lua new file mode 100644 index 0000000..958b3ee --- /dev/null +++ b/luamml-tex-annotate.lua @@ -0,0 +1,140 @@ +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 + if annotation.structnum ~= nil then + local saved = props.mathml_filter + local structnum = annotation.structnum + function props.mathml_filter(mml, core) + mml[':structnum'] = structnum + 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 diff --git a/luamml-tex.lua b/luamml-tex.lua index 36edff8..4182fbb 100644 --- a/luamml-tex.lua +++ b/luamml-tex.lua @@ -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 - else - new_mlist = mlist + local element_type = token.get_macro'l__luamml_root_tl' + if element_type ~= 'mrow' then + if xml[0] == 'mrow' then + xml[0] = element_type + else + xml = {[0] = element_type, xml} + end + end 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 + 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 - end - if buffer_tail then - mlist.prev, buffer_tail.next = nil, nil - node.flush_list(new_mlist) + 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 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, } diff --git a/luamml-xmlwriter.lua b/luamml-xmlwriter.lua index efd5f4f..ad67845 100644 --- a/luamml-xmlwriter.lua +++ b/luamml-xmlwriter.lua @@ -10,7 +10,9 @@ local escapes = { ['&'] = "&", } 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 - i = i + 1 - attrs[i] = string.format(' %s="%s"', escape_name(attr), escape_text(val)) + 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)) @@ -29,15 +34,24 @@ local function write_elem(tree, indent) return out .. '/>' end out = out .. '>' + -- Never indent the content if it's purely text. + if #tree == 1 and type(tree[1]) == 'string' then + indent = nil + end 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) + if not elem['tex:ignore'] then + out = out .. write_elem(elem, inner_indent) + end + is_string = nil end end if indent then out = out .. indent end @@ -46,5 +60,5 @@ end return function(element, indent, version) return (version == '11' and '' or '') .. - write_elem(element, indent and '' or nil) + write_elem(element, indent and '\n' or nil) end diff --git a/luamml.dtx b/luamml.dtx new file mode 100644 index 0000000..86c0d22 --- /dev/null +++ b/luamml.dtx @@ -0,0 +1,1006 @@ +% \iffalse meta-comment +% +%% Copyright (C) 2020-2025 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 +% +\input docstrip.tex +\keepsilent +\generate{ + \file{luamml.sty}{\from{luamml.dtx}{package,luatex}} + \file{luamml-pdf.sty}{\from{luamml.dtx}{package,pdftex}} +} +\endbatchfile +% +%<*gobble> +\fi +\expandafter\ifx\csname @currname\endcsname\empty +\csname fi\endcsname +% +%<*driver> +\documentclass{l3doc} +\usepackage{luamml} +\usepackage{csquotes,luacolor} +\MakeShortVerb{\|} +\RecordChanges +\ProvideDocElement[printtype=\textit{socket},idxtype=socket,idxgroup=Sockets]{Socket}{socketdecl} +\ProvideDocElement[printtype=\textit{plug},idxtype=plug,idxgroup=Plugs]{Plug}{plugdecl} + +\begin{document} +\tracingmathml2 +\DocInput{luamml.dtx} +\PrintIndex +\PrintChanges +\end{document} +% +%<*gobble> +\fi +% +% \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 |TeX| element in the output instead of trying to import \TeX~as a mathematical expression. +% +% It it possible to add a structure around the construct, stash that structure +% and then to tell \cmd{luamml_annotate:en} to insert it later inside the math. +% For this the keys \texttt{struct} (which takes a label as argument) or \texttt{structnum} +% (which takes a structure number) can be used. For example +% \begin{verbatim} +% $a = b \quad +% \tagstructbegin{tag=mtext,stash}\tagmcbegin{} +% \luamml_annotate:en{nucleus=true,structnum=\tag_get:n{struct_num}} +% {\mbox{some~text~with~\emph{structure}}} +% \tagmcend\tagstructend +% $ +% \end{verbatim} +% Such a construction should check that the flag for structure elements has actually +% been set to avoid orphaned structures if the stashed structure is ignored. +% +% More about 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} {2025-03-06} {0.5.0} + {Automatically generate presentational MathML from LuaTeX math expressions} +% +%<*pdftex> +\ProvidesExplPackage {luamml-pdf} {2025-03-06} {0.5.0} + {MathML generation for L̶u̶a̶pdfLaTeX} +% +% \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 +%\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 +%\int_new:N \g__luamml_formula_id_int +%\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} +%\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 +% +% \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 { +% 32 * #1 +%<*pdftex> + \token_case_meaning:NnF #1 { + \displaystyle {0} + \textstyle {32} + \scriptstyle {64} + \scriptscriptstyle {96} + } { + \Invalid_mathstyle + } +% +} +% \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: +% +% \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 +} +% +% \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} +% +% \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} +% +% \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 + } +} +% +%\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 + } +} +% +% \end{macrocode} +% \end{macro} +% +% \subsection{Sockets} +% In various places luamml has to add code to kernel commands. This is done through +% sockets which are predeclared in lttagging. +% +% \subsubsection{Save sockets} +% These sockets are wrappers around the \cs{luamml_save:...} commands +% They should be provided until 2025-06-01 +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/save/nn_plug_str } + { + \NewSocket{tagsupport/math/luamml/save/nn}{1} + \AssignSocketPlug{tagsupport/math/luamml/save/nn}{noop} + \NewSocket{tagsupport/math/luamml/save/nNn}{1} + \AssignSocketPlug{tagsupport/math/luamml/save/nNn}{noop} + } +% \end{macrocode} +% +% \begin{macrocode} +\NewSocketPlug{tagsupport/math/luamml/save/nNn}{luamml} + { + \luamml_save:nNn #1 + } +\AssignSocketPlug{tagsupport/math/luamml/save/nNn}{luamml} +\NewSocketPlug{tagsupport/math/luamml/save/nn}{luamml} + { + \luamml_save:nn #1 + } +\AssignSocketPlug{tagsupport/math/luamml/save/nn}{luamml} +% \end{macrocode} +% +% \subsubsection{sockets to annotate content} +% +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/annotate/false_plug_str } + { + \NewSocket{tagsupport/math/luamml/annotate/false}{2} + \NewSocketPlug{tagsupport/math/luamml/annotate/false}{default}{#2} + \AssignSocketPlug{tagsupport/math/luamml/annotate/false}{default} + } +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/annotate/false}{luamml} + { + \luamml_annotate:en { core = false } + { + #2 + } + } +\AssignSocketPlug{tagsupport/math/luamml/annotate/false}{luamml} +% +% \end{macrocode} +% \subsubsection{socket plugs for the array package} +% +% The socket declaration can go with the 2025-06-01 release +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/array/finalize_plug_str } + { + \NewSocket{tagsupport/math/luamml/array/save}{0} + \NewSocket{tagsupport/math/luamml/array/finalize}{0} + \NewSocket{tagsupport/math/luamml/array/initcol}{0} + \NewSocket{tagsupport/math/luamml/array/finalizecol}{1} + \AssignSocketPlug{tagsupport/math/luamml/array/finalizecol}{noop} + } +% \end{macrocode} +% +% The luamml support makes only sense with luatex. +% \begin{macrocode} +%<*luatex> +\AddToHook{package/array/after}{\lua_now:n { require'luamml-array' }} +% \end{macrocode} +% \begin{plugdecl}{tagsupport/math/luamml/array/save} +% The socket of this plug is used in \cs{endarray}. +% \begin{macrocode} +\NewSocketPlug{tagsupport/math/luamml/array/save}{luamml} + { + \__luamml_array_save_array: + } +% \end{macrocode} +% \end{plugdecl} +% +% \begin{plugdecl}{tagsupport/math/luamml/array/finalize} +% This socket of this plug is used in \cs{endarray}. +% \begin{macrocode} +\NewSocketPlug{tagsupport/math/luamml/array/finalize}{luamml} + { + \mode_if_math:T { \__luamml_array_finalize_array: } + } +% \end{macrocode} +% \end{plugdecl} +% +% \begin{plugdecl}{tagsupport/math/luamml/array/initcol} +% The socket of this plug is used in \cs{@classz}. +% \begin{macrocode} +\NewSocketPlug{tagsupport/math/luamml/array/initcol}{luamml} + { + \__luamml_array_init_col: + } +% \end{macrocode} +% \end{plugdecl} +% +% +% \begin{plugdecl}{tagsupport/math/luamml/array/finalizecol} +% The socket of this plug is used used in \cs{@classz}. +% \begin{macrocode} +\NewSocketPlug{tagsupport/math/luamml/array/finalizecol}{luamml} + { + \__luamml_array_finalize_col:w #1~ + } +% \end{macrocode} +% \end{plugdecl} +% \begin{macrocode} +\AssignSocketPlug{tagsupport/math/luamml/array/save}{luamml} +\AssignSocketPlug{tagsupport/math/luamml/array/finalize}{luamml} +\AssignSocketPlug{tagsupport/math/luamml/array/initcol}{luamml} +\AssignSocketPlug{tagsupport/math/luamml/array/finalizecol}{luamml} +% +% \end{macrocode} +% \subsubsection{amsmath alignments} +% +% This socket is used at the end of alignment cells and adds the content to +% the current row. +% +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/mtable/finalizecol_plug_str } + { + \NewSocket{tagsupport/math/luamml/mtable/finalizecol}{1} + } +% \end{macrocode} +% \begin{macrocode} +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/mtable/finalizecol}{luamml} + { + \use:c{__luamml_amsmath_add_#1_to_row:} + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/finalizecol}{luamml} + +% +% \end{macrocode} +% +% These sockets save an inner table +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/mtable/innertable/save_plug_str } + { + \NewSocket{tagsupport/math/luamml/mtable/innertable/save}{0} + \NewSocket{tagsupport/math/luamml/mtable/smallmatrix/save}{0} + \NewSocket{tagsupport/math/luamml/mtable/innertable/finalize}{0} + } +% \end{macrocode} +% \begin{macrocode} +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/mtable/innertable/save}{luamml} + { + \__luamml_amsmath_save_inner_table:n \@currenvir + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/innertable/save}{luamml} +\NewSocketPlug{tagsupport/math/luamml/mtable/smallmatrix/save}{luamml} + { + \__luamml_amsmath_save_smallmatrix: + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/smallmatrix/save}{luamml} +\NewSocketPlug{tagsupport/math/luamml/mtable/innertable/finalize}{luamml} + { + \__luamml_amsmath_finalize_inner_table: + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/innertable/finalize}{luamml} +% +% \end{macrocode} +% +% +% This socket finalize the \texttt{mtable} in alignments like align or gather. +% It takes an argument, the environment. +% It should be used normally with \cs{UseExpandableTaggingSocket}. +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/mtable/finalize_plug_str } + { + \NewSocket{tagsupport/math/luamml/mtable/finalize}{1} + \AssignSocketPlug{tagsupport/math/luamml/mtable/finalize}{noop} + } +% \end{macrocode} +% +% \begin{macrocode} +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/mtable/finalize}{luamml} + { + \__luamml_amsmath_finalize_table:n {#1} + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/finalize}{luamml} +% +% \end{macrocode} +% +% This socket adds attributes for the alignment in \texttt{multline}. +% It takes an argument, the alignment. +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/mtable/aligncol_plug_str } + { + \NewSocket{tagsupport/math/luamml/mtable/aligncol}{1} + \AssignSocketPlug{tagsupport/math/luamml/mtable/aligncol}{noop} + } +% \end{macrocode} +% +% \begin{macrocode} +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/mtable/aligncol}{luamml} + { + \__luamml_amsmath_set_row_columnalign:n {#1} + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/aligncol}{luamml} +% +% \end{macrocode} + +% +% \subsubsection{Tags and labels} +% These sockets save and set tags and labels in alignments. +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/mtable/tag/save_plug_str } + { + \NewSocket{tagsupport/math/luamml/mtable/tag/save}{0} + \NewSocket{tagsupport/math/luamml/mtable/tag/set}{0} + } +% \end{macrocode} +% \begin{macrocode} +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/mtable/tag/save}{luamml} + { + \__luamml_amsmath_save_tag: + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/tag/save}{luamml} +\NewSocketPlug{tagsupport/math/luamml/mtable/tag/set}{luamml} + { + \__luamml_amsmath_set_tag: + } +\AssignSocketPlug{tagsupport/math/luamml/mtable/tag/set}{luamml} + +% +% \end{macrocode} +% +% If math structure elements are created the Lbl-structure of a tag +% must be moved inside the math structure, typically as an additional column in an +% \texttt{mtable} with an intent \texttt{:equation-label} or \texttt{:no-equation-label}. +% +% The luamml-code handles this by stashing the Lbl-structure, storing the +% structure number in an array and reusing it once it creates the math structure elements. +% +% This should only be done for specific environments, we define +% a constant to test: +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/display/tag/begin_plug_str } + { + \NewSocket{tagsupport/math/display/tag/begin}{0} + \NewSocket{tagsupport/math/display/tag/end}{0} + } +% \end{macrocode} +% +% \begin{macrocode} +%<*luatex> +\clist_map_inline:nn + { + align, + align*, + alignat, + alignat*, + xalignat, + xalignat*, +% \end{macrocode} +% there is never a tag/label in xxalignat, so does it make sense to add a label column? +% Left out for now. +% \begin{macrocode} + %xxalignat, + flalign, + flalign*, + gather, + gather*, +% \end{macrocode} +% equation and multline have at most one tag, so we do not use a label column +% but rely on the external Lbl for now. +% \begin{macrocode} + %multline, % NO + %multline*, % NO + %equation, % NO + %equation*, % NO +% \end{macrocode} +% split has never a numbering so is ignored +% \begin{macrocode} + %split, % NO + } + {\tl_const:cn { c__luamml_label_#1_tl}{}} +% \end{macrocode} +% +% +% \begin{macrocode} +\NewSocketPlug{tagsupport/math/display/tag/begin}{luamml} + { + \tag_mc_end: + \bool_lazy_and:nnTF + { \tl_if_exist_p:c { c__luamml_label_ \@currenvir _tl } } + { \int_if_odd_p:n { \int_div_truncate:nn { \l__luamml_flag_int } { 8 } } } + { + %\typeout{Stash~and~move~\@currenvir\c_space_tl Lbl} + \tag_struct_begin:n {tag=Lbl,stash} + \directlua{table.insert(ltx.__tag.struct.luamml.labels,\tag_get:n{struct_num})} + } + { + \tag_struct_begin:n {tag=Lbl} + } + \tag_mc_begin:n {} + } +\AssignSocketPlug{tagsupport/math/display/tag/begin}{luamml} +% +% \end{macrocode} +% +% +% \subsubsection{Horizontal boxes} +% This socket annotates an \cs{hbox} inside box commands used in math. +% We test for the socket until the release 2025-06-01. +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/hbox_plug_str } + { + \NewSocket{tagsupport/math/luamml/hbox}{2} + \NewSocketPlug{tagsupport/math/luamml/hbox}{default}{#2} + \AssignSocketPlug{tagsupport/math/luamml/hbox}{default} + } +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/hbox}{luamml} + { + \bool_lazy_and:nnTF + { \mode_if_math_p: } + { \int_if_odd_p:n { \int_div_truncate:nn { \l__luamml_flag_int } { 8 } } } + { + \tag_struct_begin:n + { + tag=mtext, + stash, + } + \tag_mc_begin:n {} + \luamml_annotate:en + { + nucleus = true, + structnum=\tag_get:n{struct_num} + } + { #2 } + \tag_mc_end: + \tag_struct_end: + } + { #2 } + } +\AssignSocketPlug{tagsupport/math/luamml/hbox}{luamml} +% +% \end{macrocode} +% +% \subsubsection{Artifact characters} +% Unicode characters like a root sign should be marked as artifacts +% to avoid duplication e.g. in derivation if mathml +% structure elements are used that imply the meaning. +% We test for the socket until the release 2025-06-01. +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/artifact_plug_str } + { + \NewSocket{tagsupport/math/luamml/artifact}{0} + } +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/artifact}{luamml} + { + \int_if_odd:nT { \int_div_truncate:nn { \l__luamml_flag_int } { 8 } } + { + \tag_mc_begin:n{artifact} + } + } +\AssignSocketPlug{tagsupport/math/luamml/artifact}{luamml} +% +% \end{macrocode} +% +% \subsubsection{Math phantom socket} +% This socket is used around \cs{finph@nt}. +% It should provided until 2025-06-01 +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/finph@nt_plug_str } + { + \NewSocket{tagsupport/math/luamml/finph@nt}{2} + \NewSocketPlug{tagsupport/math/luamml/finph@nt}{default}{#2} + \AssignSocketPlug{tagsupport/math/luamml/finph@nt}{default} + } +% \end{macrocode} +% +% \begin{macrocode} +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/finph@nt}{luamml} + { + \luamml_annotate:nen {1} + { + nucleus = true, + core = + { + [0] = 'mpadded', + \ifh@\else + width = 0, + \fi + \ifv@\else + height = 0, depth = 0, + \fi + consume_label'mathphant', + } + } + { #2 } + } +\AssignSocketPlug{tagsupport/math/luamml/finph@nt}{luamml} +% +% \end{macrocode} + +% \subsubsection{Math smash socket} +% This socket is used around \cs{finsm@sh}. +% It should provided until 2025-06-01 +% \begin{macrocode} +\str_if_exist:cF { l__socket_tagsupport/math/luamml/finsm@sh_plug_str } + { + \NewSocket{tagsupport/math/luamml/finsm@sh}{2} + \NewSocketPlug{tagsupport/math/luamml/finsm@sh}{default}{#2} + \AssignSocketPlug{tagsupport/math/luamml/finsm@sh}{default} + } +% \end{macrocode} +% +% \begin{macrocode} +%<*luatex> +\NewSocketPlug{tagsupport/math/luamml/finsm@sh}{luamml} + { + \luamml_annotate:nen {2} + { + nucleus = true, + core = + consume_label('mathsmash', + function(padded) + padded.height, padded.depth = 0, 0~ + end), + } + { #2 } + } +\AssignSocketPlug{tagsupport/math/luamml/finsm@sh}{luamml} +% +% \end{macrocode} +% \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}. +% 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} +% +% \end{macrocode} + +% \iffalse +% +% \fi +% \end{implementation} +% \Finale diff --git a/luamml.sty b/luamml.sty deleted file mode 100644 index f563a75..0000000 --- a/luamml.sty +++ /dev/null @@ -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} diff --git a/pdfmml-emulate-node.lua b/pdfmml-emulate-node.lua new file mode 100644 index 0000000..e2209ca --- /dev/null +++ b/pdfmml-emulate-node.lua @@ -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 diff --git a/pdfmml-logreader.lua b/pdfmml-logreader.lua new file mode 100644 index 0000000..2b66e8b --- /dev/null +++ b/pdfmml-logreader.lua @@ -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 diff --git a/pdfmml-showlists.lua b/pdfmml-showlists.lua new file mode 100644 index 0000000..4d74be1 --- /dev/null +++ b/pdfmml-showlists.lua @@ -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 diff --git a/pdfmml.lua b/pdfmml.lua new file mode 100755 index 0000000..c61b018 --- /dev/null +++ b/pdfmml.lua @@ -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 diff --git a/support/luamml-algorithm.tex b/support/luamml-algorithm.tex new file mode 100644 index 0000000..0f06311 --- /dev/null +++ b/support/luamml-algorithm.tex @@ -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} +% +% 0 +% < +% x +% +% \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. diff --git a/test_tex.tex b/test_tex.tex deleted file mode 100644 index 55c341a..0000000 --- a/test_tex.tex +++ /dev/null @@ -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} diff --git a/testfiles-lua/cases.mlr b/testfiles-lua/cases.mlr new file mode 100644 index 0000000..35edb39 --- /dev/null +++ b/testfiles-lua/cases.mlr @@ -0,0 +1,142 @@ + + 𝑎 + = + 𝑏 + + + + ( + + + + + + 1 + + + 0 + + + 0 + + + + + 0 + + + 1 + + + 0 + + + + + 0 + + + 0 + + + 1 + + + + + + ) + + = + + { + + + + + 1 + + + if  + + 𝑎 + = + 𝑏 + + + + + + 2 + + + else + + + + + + + + + 𝑥 + = + + + + 𝑏 + ± + + + + 𝑏 + 2 + + + 4 + 𝑎 + 𝑐 + + + + + 2 + 𝑎 + + + . + + + + + 𝑎 + + + 𝑐 + _ + + + 𝑏 + . + + + 𝑐 + + + + + sin + ( + 𝑥 + ) + + sin + ( + 𝑥 + + + 2 + 𝜋 + ) + = + 0 + diff --git a/testfiles-lua/cases.mlt b/testfiles-lua/cases.mlt new file mode 100644 index 0000000..ff6f1a5 --- /dev/null +++ b/testfiles-lua/cases.mlt @@ -0,0 +1,44 @@ +\DocumentMetadata{ + uncompress, + pdfversion = 2.0, + testphase = {phase-III,table,math}, +} +\input{regression-test} +\documentclass{article} +\usepackage{unicode-math} + +\begin{document} +\ExplSyntaxOn +\luamml_set_filename:n { + \jobname .mml + } +\luamml_process: +\luamml_begin_single_file: +\ExplSyntaxOff + +\[ + \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} +\] + +\[ + x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}. +\] + +\[ + \sum_a\underline c\dot bc' +\] + +Es gilt $\sin(x)-\sin(x+2\pi)=0$. +\ExplSyntaxOn +\luamml_end_single_file: +\ExplSyntaxOff +\end{document} diff --git a/testfiles-lua/test_sqrt-xml.mlr b/testfiles-lua/test_sqrt-xml.mlr new file mode 100644 index 0000000..361f864 --- /dev/null +++ b/testfiles-lua/test_sqrt-xml.mlr @@ -0,0 +1,33 @@ + + + 𝑥 + + + + + + 𝑥 + + + + + 2 + + + 𝑥 + = + + + + 𝑦 + 2 + + 3 + + + + + + 𝑦 + 3 + + diff --git a/testfiles-lua/test_sqrt-xml.mlt b/testfiles-lua/test_sqrt-xml.mlt new file mode 100644 index 0000000..36a3751 --- /dev/null +++ b/testfiles-lua/test_sqrt-xml.mlt @@ -0,0 +1,28 @@ +\DocumentMetadata{ + uncompress, + pdfversion = 2.0, + testphase = {phase-III,table,math}, +} +\input{regression-test} +\documentclass{article} +\usepackage{unicode-math} + +\begin{document} +\ExplSyntaxOn +\luamml_set_filename:n { + \jobname .mml + } +\luamml_process: +\luamml_begin_single_file: +\ExplSyntaxOff + +$ \sqrt{x} $ + +$ \sqrt{\sqrt{x}} + 2 $ + +$ x = \sqrt{\sqrt[3]{y^2}} $ + +$\sqrt[3]{y}$ +\end{document} + + diff --git a/testfiles-lua/test_sqrt.pvt b/testfiles-lua/test_sqrt.pvt new file mode 100644 index 0000000..776383e --- /dev/null +++ b/testfiles-lua/test_sqrt.pvt @@ -0,0 +1,22 @@ +\ExplSyntaxOn +\sys_gset_rand_seed:n{42} +\ExplSyntaxOff +\DocumentMetadata{ + uncompress, + pdfversion = 2.0, + testphase = {phase-III,math,table}, +} +\input{regression-test} +\documentclass{article} +\usepackage{unicode-math} +\tagpdfsetup{math/mathml/structelem,attach-css=false} +\begin{document} + +$ \sqrt{x} $ + +$ \sqrt{\sqrt{x}} + 2 $ + +$ x = \sqrt{\sqrt[3]{y^2}} $ + +$\sqrt[3]{y}$ +\end{document} diff --git a/testfiles-lua/test_sqrt.tpf b/testfiles-lua/test_sqrt.tpf new file mode 100644 index 0000000..146bbb7 --- /dev/null +++ b/testfiles-lua/test_sqrt.tpf @@ -0,0 +1,990 @@ +%PDF-2.0 +% +22 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 88 >> +stream + 𝑥 +endstream +endobj +23 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-1) /F (mathml-1.xml) /UF /EF<> >> +endobj +24 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 161 >> +stream + 𝑥 + 2 +endstream +endobj +25 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-2) /F (mathml-2.xml) /UF /EF<> >> +endobj +26 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 201 >> +stream + 𝑥 = 𝑦 2 3 +endstream +endobj +27 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-3) /F (mathml-3.xml) /UF /EF<> >> +endobj +28 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 99 >> +stream + 𝑦 3 +endstream +endobj +29 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-4) /F (mathml-4.xml) /UF /EF<> >> +endobj +35 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 11 >> +stream +$\sqrt {x}$ +endstream +endobj +36 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile1.tex) /UF /EF<> >> +endobj +43 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 23 >> +stream +$\sqrt {\sqrt {x}} + 2$ +endstream +endobj +44 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile2.tex) /UF /EF<> >> +endobj +50 0 obj +<< /O/NSO/NS 13 0 R/lspace(0.222em)/rspace(0.222em) >> +endobj +55 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 28 >> +stream +$x = \sqrt {\sqrt [3]{y^2}}$ +endstream +endobj +56 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile3.tex) /UF /EF<> >> +endobj +60 0 obj +<< /O/NSO/NS 13 0 R/lspace(0.278em)/rspace(0.278em) >> +endobj +70 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 14 >> +stream +$\sqrt [3]{y}$ +endstream +endobj +71 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile4.tex) /UF /EF<> >> +endobj +76 0 obj +<< /Type /Metadata /Subtype /XML /Length 11691 >> +stream + + + + + + + + XMP Media Management Schema + xmpMM + http://ns.adobe.com/xap/1.0/mm/ + + + + OriginalDocumentID + URI + internal + The common identifier for all versions and renditions of a document. + + + + + + PDF/A Identification Schema + pdfaid + http://www.aiim.org/pdfa/ns/id/ + + + + year + Integer + internal + Year of standard + + + rev + Integer + internal + Revision year of standard + + + + + + PDF/UA Universal Accessibility Schema + pdfuaid + http://www.aiim.org/pdfua/ns/id/ + + + + part + Integer + internal + Part of ISO 14289 standard + + + rev + Integer + internal + Revision of ISO 14289 standard + + + + + + PDF/X ID Schema + pdfxid + http://www.npes.org/pdfx/ns/id/ + + + + GTS_PDFXVersion + Text + internal + ID of PDF/X standard + + + + + + PRISM Basic Metadata + prism + http://prismstandard.org/namespaces/basic/3.0/ + + + + complianceProfile + Text + internal + PRISM specification compliance profile to which this document adheres + + + publicationName + Text + external + Publication name + + + aggregationType + Text + external + Publication type + + + bookEdition + Text + external + Edition of the book in which the document was published + + + volume + Text + external + Publication volume number + + + number + Text + external + Publication issue number within a volume + + + pageRange + Text + external + Page range for the document within the print version of its publication + + + issn + Text + external + ISSN for the printed publication in which the document was published + + + eIssn + Text + external + ISSN for the electronic publication in which the document was published + + + isbn + Text + external + ISBN for the publication in which the document was published + + + doi + Text + external + Digital Object Identifier for the document + + + url + URL + external + URL at which the document can be found + + + byteCount + Integer + internal + Approximate file size in octets + + + pageCount + Integer + internal + Number of pages in the print version of the document + + + subtitle + Text + external + Document's subtitle + + + + + + + luahbtex-NN.NN.NN + 2.0 + + + Text + + + + + en + + + + + 2016-05-20T09:00:00Z + + + application/pdf + test_sqrt.tex + LaTeX + 2016-05-20T09:00:00Z + 2016-05-20T09:00:00Z + 2016-05-20T09:00:00Z + uuid:d1433a12-c113-44c0-8c00-afe828e37deb + uuid:0a57c455-157a-4141-8c19-6237d832fc80 + three + 1 + + + + +endstream +endobj +79 0 obj +<< /Length 2081 >> +stream +/opacity1 gs +/Artifact BMC +EMC +/Formula<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 148.712 664.413 Tm [<0C05>]TJ +ET +q +1 0 0 1 157.011 664.613 cm +[] 0 d 0 J 0.398 w 0 0 m 5.699 0 l S +Q +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 157.011 657.235 Tm [<0527>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 148.712 644.986 Tm [<0C09>]TJ +ET +q +1 0 0 1 158.675 653.255 cm +[] 0 d 0 J 0.398 w 0 0 m 13.998 0 l S +Q +BT +/F20 9.96264 Tf +1 0 0 1 158.675 651.412 Tm [<0C05>]TJ +ET +q +1 0 0 1 166.974 651.611 cm +[] 0 d 0 J 0.398 w 0 0 m 5.699 0 l S +Q +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 166.974 644.234 Tm [<0527>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 174.886 644.234 Tm [<000C>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 184.851 644.234 Tm [<0013>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 148.712 627.309 Tm [<0527>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 157.178 627.309 Tm [<001E>]TJ +ET +EMC +/Formula<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 167.697 628.648 Tm [<0C0A>]TJ +ET +q +1 0 0 1 177.659 639.905 cm +[] 0 d 0 J 0.398 w 0 0 m 20.272 0 l S +Q +EMC +/mn<> BDC +BT +/F22 4.98132 Tf +1 0 0 1 180.429 631.686 Tm [<0258>]TJ +ET +EMC +/Formula<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 178.282 628 Tm [<0C09>]TJ +ET +q +1 0 0 1 188.244 636.269 cm +[] 0 d 0 J 0.398 w 0 0 m 9.687 0 l S +Q +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 188.244 627.309 Tm [<0528>]TJ +ET +EMC +/mn<> BDC +BT +/F21 6.97385 Tf +1 0 0 1 193.405 630.188 Tm [<03F5>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +EMC +/mn<> BDC +BT +/F22 4.98132 Tf +1 0 0 1 151.482 616.792 Tm [<0258>]TJ +ET +EMC +/Formula<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 149.335 620.379 Tm [<0C05>]TJ +ET +q +1 0 0 1 157.634 620.578 cm +[] 0 d 0 J 0.398 w 0 0 m 4.882 0 l S +Q +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 157.634 614.167 Tm [<0528>]TJ +ET +EMC +/Artifact BMC +EMC +/Artifact BMC +BT +/F15 9.96264 Tf +1 0 0 1 303.133 89.365 Tm [<0052>]TJ +ET +EMC +/Artifact BMC +EMC +endstream +endobj +78 0 obj +<< /Type /Page /Contents 79 0 R /Resources 77 0 R /MediaBox [ 0 0 612 792 ] /StructParents 0/Tabs /S /Parent 84 0 R >> +endobj +77 0 obj +<< /ExtGState 1 0 R /Font << /F20 80 0 R /F22 81 0 R /F21 82 0 R /F15 83 0 R >> >> +endobj +1 0 obj +<< /opacity1 <> >> +endobj +85 0 obj +<< /Marked true >> +endobj +86 0 obj +<< /Names[(l3ef0001) 23 0 R (l3ef0002) 25 0 R (l3ef0003) 27 0 R (l3ef0004) 29 0 R] >> +endobj +6 0 obj +<< /Nums [0 [ 34 0 R 39 0 R 42 0 R 48 0 R 49 0 R 51 0 R 54 0 R 58 0 R 59 0 R 54 0 R 66 0 R 54 0 R 64 0 R 65 0 R 69 0 R 75 0 R 69 0 R 74 0 R] +] >> +endobj +87 0 obj +<< /Limits [(ID.002) (ID.038)]/Names [(ID.002) 21 0 R (ID.003) 30 0 R (ID.004) 31 0 R (ID.005) 32 0 R (ID.006) 33 0 R (ID.007) 34 0 R (ID.008) 37 0 R (ID.009) 38 0 R (ID.010) 39 0 R (ID.011) 40 0 R (ID.012) 41 0 R (ID.013) 42 0 R (ID.014) 45 0 R (ID.015) 46 0 R (ID.016) 47 0 R (ID.017) 48 0 R (ID.018) 49 0 R (ID.019) 51 0 R (ID.020) 52 0 R (ID.021) 53 0 R (ID.022) 54 0 R (ID.023) 57 0 R (ID.024) 58 0 R (ID.025) 59 0 R (ID.026) 61 0 R (ID.027) 62 0 R (ID.028) 63 0 R (ID.029) 64 0 R (ID.030) 65 0 R (ID.031) 66 0 R (ID.032) 67 0 R (ID.033) 68 0 R (ID.034) 69 0 R (ID.035) 72 0 R (ID.036) 73 0 R (ID.037) 74 0 R (ID.038) 75 0 R ] >> +endobj +88 0 obj +<< /Kids [87 0 R] >> +endobj +7 0 obj +<< /Artifact /NonStruct /DocumentFragment /Art /Aside /Note /H7 /H6 /H8 /H6 /H9 /H6 /H10 /H6 /Title /P /FENote /Note /Sub /Span /Em /Span /Strong /Span /title /P /part /P /section /H1 /subsection /H2 /subsubsection /H3 /paragraph /H4 /subparagraph /H5 /list /L /itemize /L /enumerate /L /description /L /quote /BlockQuote /quotation /BlockQuote /verbatim /P /item /LI /itemlabel /Lbl /itembody /LBody /footnote /Note /footnotemark /Lbl /footnotelabel /Lbl /text-unit /Part /text /P /theorem-like /Sect /codeline /Span /float /Note /figures /Sect /tables /Sect >> +endobj +89 0 obj +<< /justify <> +/inline <> +/TH-both <> +/TH-row <> +/TH-col <> + >> +endobj +9 0 obj +<< /Type /Namespace /NS (http://iso.org/pdf/ssn) >> +endobj +11 0 obj +<< /Type /Namespace /NS (http://iso.org/pdf2/ssn) >> +endobj +13 0 obj +<< /Type /Namespace /NS (http://www.w3.org/1998/Math/MathML) >> +endobj +16 0 obj +<< /title [/Title 11 0 R] /part [/Title 11 0 R] /section [/H1 11 0 R] /subsection [/H2 11 0 R] /subsubsection [/H3 11 0 R] /paragraph [/H4 11 0 R] /subparagraph [/H5 11 0 R] /list [/L 11 0 R] /itemize [/L 11 0 R] /enumerate [/L 11 0 R] /description [/L 11 0 R] /quote [/BlockQuote 9 0 R] /quotation [/BlockQuote 9 0 R] /verbatim [/P 11 0 R] /item [/LI 11 0 R] /itemlabel [/Lbl 11 0 R] /itembody [/LBody 11 0 R] /footnote [/FENote 11 0 R] /footnotemark [/Lbl 11 0 R] /footnotelabel [/Lbl 11 0 R] /text-unit [/Part 11 0 R] /text [/P 11 0 R] /theorem-like [/Sect 11 0 R] /codeline [/Sub 11 0 R] /float [/Aside 11 0 R] /figures [/Sect 11 0 R] /tables [/Sect 11 0 R] >> +endobj +15 0 obj +<< /Type /Namespace /NS (https://www.latex-project.org/ns/dflt) /RoleMapNS 16 0 R >> +endobj +18 0 obj +<< /chapter [/H1 11 0 R] /section [/H2 11 0 R] /subsection [/H3 11 0 R] /subsubsection [/H4 11 0 R] /paragraph [/H5 11 0 R] /subparagraph [/H6 11 0 R] >> +endobj +17 0 obj +<< /Type /Namespace /NS (https://www.latex-project.org/ns/book) /RoleMapNS 18 0 R >> +endobj +19 0 obj +<< /Type /Namespace /NS (data:,C9B55C18-275C-494E-C10F-E4B83CD03F23) >> +endobj +8 0 obj +[ 9 0 R 11 0 R 13 0 R 15 0 R 17 0 R 19 0 R ] +endobj +21 0 obj +<< /Type /StructElem /S /Document /NS 11 0 R /P 5 0 R /K [32 0 R 40 0 R 52 0 R 67 0 R] /ID (ID.002) >> +endobj +30 0 obj +<< /Type /StructElem /S /Artifact /NS 15 0 R /P 5 0 R /ID (ID.003) >> +endobj +31 0 obj +<< /Type /StructElem /S /Artifact /NS 15 0 R /P 5 0 R /ID (ID.004) >> +endobj +32 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 33 0 R /ID (ID.005) >> +endobj +33 0 obj +<< /Type /StructElem /C /justify /S /text /NS 15 0 R /P 32 0 R /K [ 34 0 R ] /ID (ID.006) >> +endobj +34 0 obj +<< /Type /StructElem /C /inline /AF [23 0 R 36 0 R] /T /S /Formula /NS 11 0 R /P 33 0 R /K [<> 37 0 R] /ID (ID.007) >> +endobj +37 0 obj +<< /Type /StructElem /S /math /NS 13 0 R /P 34 0 R /K 38 0 R /ID (ID.008) >> +endobj +38 0 obj +<< /Type /StructElem /S /msqrt /NS 13 0 R /P 37 0 R /K 39 0 R /ID (ID.009) >> +endobj +39 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 38 0 R /K <> /ID (ID.010) >> +endobj +40 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 41 0 R /ID (ID.011) >> +endobj +41 0 obj +<< /Type /StructElem /C /justify /S /text /NS 15 0 R /P 40 0 R /K [ 42 0 R ] /ID (ID.012) >> +endobj +42 0 obj +<< /Type /StructElem /C /inline /AF [25 0 R 44 0 R] /T /S /Formula /NS 11 0 R /P 41 0 R /K [<> 45 0 R] /ID (ID.013) >> +endobj +45 0 obj +<< /Type /StructElem /S /math /NS 13 0 R /P 42 0 R /K [46 0 R 49 0 R 51 0 R] /ID (ID.014) >> +endobj +46 0 obj +<< /Type /StructElem /S /msqrt /NS 13 0 R /P 45 0 R /K 47 0 R /ID (ID.015) >> +endobj +47 0 obj +<< /Type /StructElem /S /msqrt /NS 13 0 R /P 46 0 R /K 48 0 R /ID (ID.016) >> +endobj +48 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 47 0 R /K <> /ID (ID.017) >> +endobj +49 0 obj +<< /Type /StructElem /A 50 0 R /S /mo /NS 13 0 R /P 45 0 R /K <> /ID (ID.018) >> +endobj +51 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 45 0 R /K <> /ID (ID.019) >> +endobj +52 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 53 0 R /ID (ID.020) >> +endobj +53 0 obj +<< /Type /StructElem /C /justify /S /text /NS 15 0 R /P 52 0 R /K [ 54 0 R ] /ID (ID.021) >> +endobj +54 0 obj +<< /Type /StructElem /C /inline /AF [27 0 R 56 0 R] /T /S /Formula /NS 11 0 R /P 53 0 R /K [<> <> <> 57 0 R] /ID (ID.022) >> +endobj +57 0 obj +<< /Type /StructElem /S /math /NS 13 0 R /P 54 0 R /K [58 0 R 59 0 R 61 0 R] /ID (ID.023) >> +endobj +58 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 57 0 R /K <> /ID (ID.024) >> +endobj +59 0 obj +<< /Type /StructElem /A 60 0 R /S /mo /NS 13 0 R /P 57 0 R /K <> /ID (ID.025) >> +endobj +61 0 obj +<< /Type /StructElem /S /msqrt /NS 13 0 R /P 57 0 R /K 62 0 R /ID (ID.026) >> +endobj +62 0 obj +<< /Type /StructElem /S /mroot /NS 13 0 R /P 61 0 R /K [63 0 R 66 0 R] /ID (ID.027) >> +endobj +63 0 obj +<< /Type /StructElem /S /msup /NS 13 0 R /P 62 0 R /K [64 0 R 65 0 R] /ID (ID.028) >> +endobj +64 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 63 0 R /K <> /ID (ID.029) >> +endobj +65 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 63 0 R /K <> /ID (ID.030) >> +endobj +66 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 62 0 R /K <> /ID (ID.031) >> +endobj +67 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 68 0 R /ID (ID.032) >> +endobj +68 0 obj +<< /Type /StructElem /C /justify /S /text /NS 15 0 R /P 67 0 R /K [ 69 0 R ] /ID (ID.033) >> +endobj +69 0 obj +<< /Type /StructElem /C /inline /AF [29 0 R 71 0 R] /T /S /Formula /NS 11 0 R /P 68 0 R /K [<> <> 72 0 R] /ID (ID.034) >> +endobj +72 0 obj +<< /Type /StructElem /S /math /NS 13 0 R /P 69 0 R /K 73 0 R /ID (ID.035) >> +endobj +73 0 obj +<< /Type /StructElem /S /mroot /NS 13 0 R /P 72 0 R /K [74 0 R 75 0 R] /ID (ID.036) >> +endobj +74 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 73 0 R /K <> /ID (ID.037) >> +endobj +75 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 73 0 R /K <> /ID (ID.038) >> +endobj +5 0 obj +<< /Type /StructTreeRoot /Namespaces 8 0 R /IDTree 88 0 R /ClassMap 89 0 R /ParentTree 6 0 R /RoleMap 7 0 R /K 21 0 R >> +endobj +90 0 obj +[ 82 [ 500 ] ] +endobj +92 0 obj +<< /Subtype /CIDFontType0C /Length 574 >> +[BINARY STREAM] +endobj +91 0 obj +<< /Type /FontDescriptor /FontName /JFRMQG+LMRoman10-Regular /Flags 4 /FontBBox [ -430 -290 1417 1127 ] /Ascent 1127 /CapHeight 683 /Descent -290 /ItalicAngle 0 /StemV 93 /XHeight 431 /FontFile3 92 0 R >> +endobj +93 0 obj +<< /Length 692 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-JFRMQG-LMRoman10-Regular-0) +%%Title: (TeX-JFRMQG-LMRoman10-Regular-0 TeX JFRMQG-LMRoman10-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (JFRMQG-LMRoman10-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-JFRMQG-LMRoman10-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +1 beginbfchar +<0052> <0031> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +83 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /JFRMQG+LMRoman10-Regular /DescendantFonts [ 94 0 R ] /ToUnicode 93 0 R >> +endobj +94 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /JFRMQG+LMRoman10-Regular /FontDescriptor 91 0 R /W 90 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +95 0 obj +[ 1013 [ 569 ] ] +endobj +97 0 obj +<< /Subtype /CIDFontType0C /Length 702 >> +[BINARY STREAM] +endobj +96 0 obj +<< /Type /FontDescriptor /FontName /MGWLKY+LatinModernMath-Regular /Flags 4 /FontBBox [ -1042 -3060 4082 3560 ] /Ascent 806 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 113 /XHeight 431 /FontFile3 97 0 R >> +endobj +98 0 obj +<< /Length 722 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-MGWLKY-LatinModernMath-Regular-0) +%%Title: (TeX-MGWLKY-LatinModernMath-Regular-0 TeX MGWLKY-LatinModernMath-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (MGWLKY-LatinModernMath-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-MGWLKY-LatinModernMath-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +1 beginbfchar +<03F5> <0032> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +82 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /MGWLKY+LatinModernMath-Regular /DescendantFonts [ 99 0 R ] /ToUnicode 98 0 R >> +endobj +99 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /MGWLKY+LatinModernMath-Regular /FontDescriptor 96 0 R /W 95 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +100 0 obj +[ 600 [ 681 ] ] +endobj +102 0 obj +<< /Subtype /CIDFontType0C /Length 743 >> +[BINARY STREAM] +endobj +101 0 obj +<< /Type /FontDescriptor /FontName /DPSGTM+LatinModernMath-Regular /Flags 4 /FontBBox [ -1042 -3060 4082 3560 ] /Ascent 806 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 153 /XHeight 431 /FontFile3 102 0 R >> +endobj +103 0 obj +<< /Length 722 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-DPSGTM-LatinModernMath-Regular-0) +%%Title: (TeX-DPSGTM-LatinModernMath-Regular-0 TeX DPSGTM-LatinModernMath-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (DPSGTM-LatinModernMath-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-DPSGTM-LatinModernMath-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +1 beginbfchar +<0258> <0033> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +81 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /DPSGTM+LatinModernMath-Regular /DescendantFonts [ 104 0 R ] /ToUnicode 103 0 R >> +endobj +104 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /DPSGTM+LatinModernMath-Regular /FontDescriptor 101 0 R /W 100 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +105 0 obj +[ 12 [ 778 ] 19 [ 500 ] 30 [ 778 ] 1319 [ 572 490 ] 3077 [ 833 ] 3081 [ 1000 1000 ] ] +endobj +107 0 obj +<< /Subtype /CIDFontType0C /Length 1511 >> +[BINARY STREAM] +endobj +106 0 obj +<< /Type /FontDescriptor /FontName /NDJELI+LatinModernMath-Regular /Flags 4 /FontBBox [ -1042 -3060 4082 3560 ] /Ascent 806 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 93 /XHeight 431 /FontFile3 107 0 R >> +endobj +108 0 obj +<< /Length 828 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-NDJELI-LatinModernMath-Regular-0) +%%Title: (TeX-NDJELI-LatinModernMath-Regular-0 TeX NDJELI-LatinModernMath-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (NDJELI-LatinModernMath-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-NDJELI-LatinModernMath-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +8 beginbfchar +<000C> <002B> +<0013> <0032> +<001E> <003D> +<0527> +<0528> +<0C05> <221A> +<0C09> <221A> +<0C0A> <221A> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +80 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /NDJELI+LatinModernMath-Regular /DescendantFonts [ 109 0 R ] /ToUnicode 108 0 R >> +endobj +109 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /NDJELI+LatinModernMath-Regular /FontDescriptor 106 0 R /W 105 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +84 0 obj +<< /Type /Pages /Count 1 /Kids [ 78 0 R ] >> +endobj +110 0 obj +<< /EmbeddedFiles 86 0 R >> +endobj +111 0 obj +<< /Type /Catalog /Pages 84 0 R /Names 110 0 R /MarkInfo 85 0 R/Lang (en)/Metadata 76 0 R/StructTreeRoot 5 0 R >> +endobj +112 0 obj +<< /Producer (LuaTeX)/Creator (TeX)/CreationDate (D:20160520090000Z)/ModDate (D:20160520090000Z) /Trapped /False >> +endobj +xref +0 113 +0000000002 65535 f +0000017659 00000 n +0000000003 00000 f +0000000004 00000 f +0000000010 00000 f +0000025382 00000 n +0000017844 00000 n +0000018694 00000 n +0000020839 00000 n +0000019474 00000 n +0000000012 00000 f +0000019542 00000 n +0000000014 00000 f +0000019612 00000 n +0000000020 00000 f +0000020375 00000 n +0000019693 00000 n +0000020648 00000 n +0000020477 00000 n +0000020750 00000 n +0000000000 00000 f +0000020900 00000 n +0000000020 00000 n +0000000257 00000 n +0000000444 00000 n +0000000754 00000 n +0000000941 00000 n +0000001291 00000 n +0000001478 00000 n +0000001726 00000 n +0000021021 00000 n +0000021109 00000 n +0000021197 00000 n +0000021297 00000 n +0000021408 00000 n +0000001913 00000 n +0000002067 00000 n +0000021600 00000 n +0000021695 00000 n +0000021791 00000 n +0000021912 00000 n +0000022012 00000 n +0000022123 00000 n +0000002267 00000 n +0000002433 00000 n +0000022315 00000 n +0000022426 00000 n +0000022522 00000 n +0000022618 00000 n +0000022739 00000 n +0000002633 00000 n +0000022871 00000 n +0000022992 00000 n +0000023092 00000 n +0000023203 00000 n +0000002704 00000 n +0000002875 00000 n +0000023464 00000 n +0000023575 00000 n +0000023696 00000 n +0000003075 00000 n +0000023828 00000 n +0000023924 00000 n +0000024029 00000 n +0000024133 00000 n +0000024255 00000 n +0000024377 00000 n +0000024499 00000 n +0000024599 00000 n +0000024710 00000 n +0000003146 00000 n +0000003303 00000 n +0000024938 00000 n +0000025033 00000 n +0000025138 00000 n +0000025260 00000 n +0000003503 00000 n +0000017560 00000 n +0000017425 00000 n +0000015284 00000 n +0000034795 00000 n +0000031606 00000 n +0000029365 00000 n +0000027181 00000 n +0000035166 00000 n +0000017706 00000 n +0000017742 00000 n +0000018006 00000 n +0000018657 00000 n +0000019273 00000 n +0000025519 00000 n +0000026208 00000 n +0000025550 00000 n +0000026429 00000 n +0000027335 00000 n +0000027535 00000 n +0000028354 00000 n +0000027568 00000 n +0000028583 00000 n +0000029525 00000 n +0000029731 00000 n +0000030592 00000 n +0000029764 00000 n +0000030823 00000 n +0000031768 00000 n +0000031977 00000 n +0000033676 00000 n +0000032080 00000 n +0000033906 00000 n +0000034957 00000 n +0000035228 00000 n +0000035273 00000 n +0000035404 00000 n +trailer +<< /Size 113 /Root 111 0 R /Info 112 0 R /ID [ <2350CAD05F8A7AF0AA4058486855344F> <2350CAD05F8A7AF0AA4058486855344F> ] >> +startxref +35537 +%%EOF diff --git a/testfiles-lua/test_struct.pvt b/testfiles-lua/test_struct.pvt new file mode 100644 index 0000000..d78a16c --- /dev/null +++ b/testfiles-lua/test_struct.pvt @@ -0,0 +1,47 @@ +\ExplSyntaxOn +\sys_gset_rand_seed:n{42} +\ExplSyntaxOff +\DocumentMetadata{ + uncompress, + pdfversion = 2.0, + testphase = {phase-III,math,table}, +} +\input{regression-test} +\documentclass{article} +\usepackage{unicode-math} +\tagpdfsetup{math/mathml/structelem,attach-css=false} +\begin{document} + +hello + +\[ + \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} +\] + +\[ + x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}. +\] + +\[ + \sum_a\underline c\dot bc' +\] + +\begin{align} + abc&=def & e^{\mathrm{i}\pi}&=-1\\ + \Big(1+2&=3\Big)\\ + 5 +\end{align} + + +Es gilt $\sin(x)-\sin(x+2\pi)=0$. + +\end{document} diff --git a/testfiles-lua/test_struct.tpf b/testfiles-lua/test_struct.tpf new file mode 100644 index 0000000..21ec100 --- /dev/null +++ b/testfiles-lua/test_struct.tpf @@ -0,0 +1,2058 @@ +%PDF-2.0 +% +22 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 130 >> +stream + 𝑎 = 𝑏 +endstream +endobj +23 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-1) /F (mathml-1.xml) /UF /EF<> >> +endobj +24 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 496 >> +stream + 𝑥 = 𝑏 ± 𝑏 2 4 𝑎 𝑐 2 𝑎 . +endstream +endobj +25 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-2) /F (mathml-2.xml) /UF /EF<> >> +endobj +26 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 333 >> +stream + 𝑎 𝑐 _ 𝑏 . 𝑐 +endstream +endobj +27 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-3) /F (mathml-3.xml) /UF /EF<> >> +endobj +28 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 1203 >> +stream + 𝑎 𝑏 𝑐 = 𝑑 𝑒 𝑓 𝑒 i 𝜋 = 1 ( 1 + 2 = 3 ) 5 +endstream +endobj +29 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-4) /F (mathml-4.xml) /UF /EF<> >> +endobj +30 0 obj +<< /Type /EmbeddedFile /Subtype /application#2Fmathml+xml /Params<> /Length 530 >> +stream + sin ( 𝑥 ) sin ( 𝑥 + 2 𝜋 ) = 0 +endstream +endobj +31 0 obj +<< /Type /Filespec /AFRelationship /Supplement /Desc (mathml-5) /F (mathml-5.xml) /UF /EF<> >> +endobj +38 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 172 >> +stream +\begin {equation*}\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}\end {equation*} +endstream +endobj +39 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile1.tex) /UF /EF<> >> +endobj +64 0 obj +<< /O/NSO/NS 13 0 R/lspace(0.278em)/rspace(0.278em) >> +endobj +72 0 obj +<< /O/NSO/NS 13 0 R/display(block) >> +endobj +75 0 obj +<< /O/NSO/NS 13 0 R/fence(true)/lspace(0)/rspace(0)/symmetric(true) >> +endobj +77 0 obj +<< /O/NSO/NS 13 0 R/width(-4.981pt) >> +endobj +79 0 obj +<< /O/NSO/NS 13 0 R/lspace(+4.981pt)/width(+9.963pt) >> +endobj +94 0 obj +<< /O/NSO/NS 13 0 R/width(1.196pt) >> +endobj +97 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 73 >> +stream +\begin {equation*}x = \frac {-b \pm \sqrt {b^2-4ac}}{2a}.\end {equation*} +endstream +endobj +98 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile2.tex) /UF /EF<> >> +endobj +105 0 obj +<< /O/NSO/NS 13 0 R/lspace(0)/rspace(0.222em) >> +endobj +108 0 obj +<< /O/NSO/NS 13 0 R/lspace(0.222em)/rspace(0.222em) >> +endobj +122 0 obj +<< /O/NSO/NS 13 0 R/lspace(0)/rspace(0) >> +endobj +125 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 61 >> +stream +\begin {equation*}\sum _a\underline c\dot bc'\end {equation*} +endstream +endobj +126 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile3.tex) /UF /EF<> >> +endobj +130 0 obj +<< /O/NSO/NS 13 0 R/lspace(0)/movablelimits(true)/rspace(0.167em) >> +endobj +138 0 obj +<< /O/NSO/NS 13 0 R/stretchy(true) >> +endobj +142 0 obj +<< /O/NSO/NS 13 0 R/mathvariant(normal) >> +endobj +145 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 85 >> +stream +\begin {align}abc&=def & e^{\mathrm {i}\pi }&=-1\\ \Big (1+2&=3\Big )\\ 5\end {align} +endstream +endobj +146 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile4.tex) /UF /EF<> >> +endobj +164 0 obj +<< /O/NSO/NS 13 0 R/lspace(0.278em)/rspace(0) >> +endobj +170 0 obj +<< /O/NSO/NS 13 0 R/fence(true)/lspace(0)/maxsize(17.861pt)/minsize(17.861pt)/rspace(0)/symmetric(true) >> +endobj +189 0 obj +<< /O/NSO/NS 13 0 R/class(align)/columnalign(left right left right left)/columnspacing(0 0 .8em 0)/displaystyle(true)/intent(:system-of-equations) >> +endobj +196 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 27 >> +stream +$\sin (x)-\sin (x+2\pi )=0$ +endstream +endobj +197 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile5.tex) /UF /EF<> >> +endobj +201 0 obj +<< /O/NSO/NS 13 0 R/lspace(0)/rspace(0)/stretchy(false) >> +endobj +214 0 obj +<< /Type /Metadata /Subtype /XML /Length 11693 >> +stream + + + + + + + + XMP Media Management Schema + xmpMM + http://ns.adobe.com/xap/1.0/mm/ + + + + OriginalDocumentID + URI + internal + The common identifier for all versions and renditions of a document. + + + + + + PDF/A Identification Schema + pdfaid + http://www.aiim.org/pdfa/ns/id/ + + + + year + Integer + internal + Year of standard + + + rev + Integer + internal + Revision year of standard + + + + + + PDF/UA Universal Accessibility Schema + pdfuaid + http://www.aiim.org/pdfua/ns/id/ + + + + part + Integer + internal + Part of ISO 14289 standard + + + rev + Integer + internal + Revision of ISO 14289 standard + + + + + + PDF/X ID Schema + pdfxid + http://www.npes.org/pdfx/ns/id/ + + + + GTS_PDFXVersion + Text + internal + ID of PDF/X standard + + + + + + PRISM Basic Metadata + prism + http://prismstandard.org/namespaces/basic/3.0/ + + + + complianceProfile + Text + internal + PRISM specification compliance profile to which this document adheres + + + publicationName + Text + external + Publication name + + + aggregationType + Text + external + Publication type + + + bookEdition + Text + external + Edition of the book in which the document was published + + + volume + Text + external + Publication volume number + + + number + Text + external + Publication issue number within a volume + + + pageRange + Text + external + Page range for the document within the print version of its publication + + + issn + Text + external + ISSN for the printed publication in which the document was published + + + eIssn + Text + external + ISSN for the electronic publication in which the document was published + + + isbn + Text + external + ISBN for the publication in which the document was published + + + doi + Text + external + Digital Object Identifier for the document + + + url + URL + external + URL at which the document can be found + + + byteCount + Integer + internal + Approximate file size in octets + + + pageCount + Integer + internal + Number of pages in the print version of the document + + + subtitle + Text + external + Document's subtitle + + + + + + + luahbtex-NN.NN.NN + 2.0 + + + Text + + + + + en + + + + + 2016-05-20T09:00:00Z + + + application/pdf + test_struct.tex + LaTeX + 2016-05-20T09:00:00Z + 2016-05-20T09:00:00Z + 2016-05-20T09:00:00Z + uuid:0629f6ef-2113-4b62-83f8-5725311a8337 + uuid:0a57c455-157a-4141-8c19-6237d832fc80 + three + 1 + + + + +endstream +endobj +217 0 obj +<< /Length 8458 >> +stream +/opacity1 gs +/Artifact BMC +EMC +/text<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 148.712 657.235 Tm [<003F0032004800480051>]TJ +ET +EMC +/Span<>> BDC +BT +/F20 9.96264 Tf +1 0 0 1 244.283 627.612 Tm [<09C9>]TJ +1 0 0 1 244.283 623.87 Tm [<09C8>]TJ +1 0 0 1 244.283 610.196 Tm [<09C7>]TJ +ET +EMC +/Formula<> BDC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 253.001 635.915 Tm [<0012>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 267.945 635.915 Tm [<0011>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 282.889 635.915 Tm [<0011>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 253.001 623.96 Tm [<0011>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 267.945 623.96 Tm [<0012>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 282.889 623.96 Tm [<0011>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 253.001 612.005 Tm [<0011>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 267.945 612.005 Tm [<0011>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 282.889 612.005 Tm [<0012>]TJ +ET +EMC +/Artifact BMC +EMC +/Span<>> BDC +BT +/F20 9.96264 Tf +1 0 0 1 287.87 627.612 Tm [<09CC>]TJ +1 0 0 1 287.87 623.87 Tm [<09CB>]TJ +1 0 0 1 287.87 610.196 Tm [<09CA>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 299.355 623.861 Tm [<001E>]TJ +ET +EMC +/Span<>> BDC +BT +/F20 9.96264 Tf +1 0 0 1 309.873 623.861 Tm [<09D3>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 318.859 630.655 Tm [<0012>]TJ +ET +EMC +/Artifact BMC +EMC +/mtext<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 333.803 630.655 Tm [<004200370067>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 342.939 630.655 Tm [<0510>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 350.977 630.655 Tm [<001E>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 361.495 630.655 Tm [<0511>]TJ +ET +EMC +/Artifact BMC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 318.859 616.309 Tm [<0013>]TJ +ET +EMC +/Artifact BMC +EMC +/mtext<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 333.803 616.309 Tm [<0032004800620032>]TJ +ET +EMC +/Artifact BMC +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 259.389 584.175 Tm [<0527>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 267.855 584.175 Tm [<001E>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 279.569 590.92 Tm [<0A37>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 287.319 590.92 Tm [<0511>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 293.947 590.92 Tm [<0A3B>]TJ +ET +EMC +/Formula<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 303.912 599.651 Tm [<0C05>]TJ +ET +q +1 0 0 1 312.21 599.851 cm +[] 0 d 0 J 0.398 w 0 0 m 35.684 0 l S +Q +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 312.21 590.92 Tm [<0511>]TJ +ET +EMC +/mn<> BDC +BT +/F21 6.97385 Tf +1 0 0 1 316.624 593.799 Tm [<03F5>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 323.364 590.92 Tm [<0A37>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 333.329 590.92 Tm [<0015>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 338.31 590.92 Tm [<0510>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 343.58 590.92 Tm [<0512>]TJ +ET +EMC +/Formula<> BDC +q +1 0 0 1 279.569 586.666 cm +[] 0 d 0 J 0.398 w 0 0 m 68.325 0 l S +Q +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 308.605 577.341 Tm [<0013>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 313.587 577.341 Tm [<0510>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 349.089 584.175 Tm [<000F>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 289.257 554.287 Tm [<0C02>]TJ +ET +EMC +/mi<> BDC +BT +/F21 6.97385 Tf +1 0 0 1 294.289 543.827 Tm [<057C>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 305.304 554.287 Tm [<0512>]TJ +ET +EMC +/Formula<> BDC +q +1 0 0 1 305.304 552.783 cm +[] 0 d 0 J 0.398 w 0 0 m 4.314 0 l S +Q +EMC +/Span<>> BDC +BT +/F20 9.96264 Tf +1 0 0 1 313.902 556.718 Tm [<06FE>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 309.618 554.287 Tm [<0511>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 314.031 554.287 Tm [<0512>]TJ +ET +EMC +/mi<> BDC +BT +/F21 6.97385 Tf +1 0 0 1 318.594 557.904 Tm [<0BA5>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 231.386 514.437 Tm [<0510>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 236.656 514.437 Tm [<0511>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 241.07 514.437 Tm [<0512>]TJ +ET +EMC +/Artifact BMC +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 248.151 514.437 Tm [<001E>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 258.669 514.437 Tm [<0513>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 264.089 514.437 Tm [<0514>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 268.731 514.437 Tm [<0515>]TJ +ET +EMC +/Artifact BMC +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 356.481 514.437 Tm [<0514>]TJ +ET +EMC +/mi<> BDC +BT +/F32 6.97385 Tf +1 0 0 1 361.124 518.053 Tm [<0042>]TJ +ET +EMC +/mi<> BDC +BT +/F21 6.97385 Tf +1 0 0 1 363.377 518.053 Tm [<11CE>]TJ +ET +EMC +/Artifact BMC +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 371.36 514.437 Tm [<001E>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 381.879 514.437 Tm [<0A37>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 389.63 514.437 Tm [<0012>]TJ +ET +EMC +/Artifact BMC +EMC +/Lbl<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 464.747 514.437 Tm [<005500520056>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +EMC +/Span<>> BDC +BT +/F20 9.96264 Tf +1 0 0 1 216.637 493.954 Tm [<0997>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 223.242 493.954 Tm [<0012>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 230.437 493.954 Tm [<000C>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 240.402 493.954 Tm [<0013>]TJ +ET +EMC +/Artifact BMC +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 248.151 493.954 Tm [<001E>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 258.669 493.954 Tm [<0014>]TJ +ET +EMC +/Span<>> BDC +BT +/F20 9.96264 Tf +1 0 0 1 263.65 493.954 Tm [<0998>]TJ +ET +EMC +/Artifact BMC +EMC +/Lbl<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 464.747 493.954 Tm [<0055006B0056>]TJ +ET +EMC +/Artifact BMC +EMC +/Formula<> BDC +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 240.402 473.67 Tm [<0016>]TJ +ET +EMC +/Artifact BMC +EMC +/Lbl<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 464.747 473.67 Tm [<0055006A0056>]TJ +ET +EMC +/text<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 148.712 451.752 Tm [<003100620067003B0042004800690067>]TJ +ET +EMC +/Formula<> BDC +EMC +/mi<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 180.453 451.752 Tm [<00620042004D>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 192.687 451.752 Tm [<0009>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 196.563 451.752 Tm [<0527>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 202.262 451.752 Tm [<000A>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 208.351 451.752 Tm [<0A37>]TJ +ET +EMC +/mi<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 218.316 451.752 Tm [<00620042004D>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 230.55 451.752 Tm [<0009>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 234.425 451.752 Tm [<0527>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 242.338 451.752 Tm [<000C>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 252.303 451.752 Tm [<0013>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 257.284 451.752 Tm [<117A>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 263.212 451.752 Tm [<000A>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 269.854 451.752 Tm [<001E>]TJ +ET +EMC +/mn<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 280.373 451.752 Tm [<0011>]TJ +ET +EMC +/Artifact BMC +EMC +/text<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 285.354 451.752 Tm [<0058>]TJ +ET +EMC +/Artifact BMC +EMC +/Artifact BMC +BT +/F15 9.96264 Tf +1 0 0 1 303.133 89.365 Tm [<0052>]TJ +ET +EMC +/Artifact BMC +EMC +endstream +endobj +216 0 obj +<< /Type /Page /Contents 217 0 R /Resources 215 0 R /MediaBox [ 0 0 612 792 ] /StructParents 0/Tabs /S /Parent 222 0 R >> +endobj +215 0 obj +<< /ExtGState 1 0 R /Font << /F15 218 0 R /F20 219 0 R /F21 220 0 R /F32 221 0 R >> >> +endobj +1 0 obj +<< /opacity1 <> >> +endobj +223 0 obj +<< /Marked true >> +endobj +224 0 obj +<< /Names[(l3ef0001) 23 0 R (l3ef0002) 25 0 R (l3ef0003) 27 0 R (l3ef0004) 29 0 R (l3ef0005) 31 0 R] >> +endobj +6 0 obj +<< /Nums [0 [ 35 0 R 74 0 R 37 0 R 41 0 R 43 0 R 45 0 R 37 0 R 47 0 R 49 0 R 51 0 R 37 0 R 53 0 R 55 0 R 57 0 R 85 0 R 86 0 R 88 0 R 59 0 R 60 0 R 62 0 R 63 0 R 65 0 R 68 0 R 69 0 R 100 0 R 101 0 R 104 0 R 106 0 R 107 0 R 96 0 R 112 0 R 113 0 R 114 0 R 115 0 R 116 0 R 117 0 R 96 0 R 119 0 R 120 0 R 121 0 R 129 0 R 131 0 R 133 0 R 124 0 R 137 0 R 136 0 R 140 0 R 141 0 R 144 0 R 148 0 R 149 0 R 150 0 R 152 0 R 153 0 R 154 0 R 155 0 R 158 0 R 160 0 R 161 0 R 163 0 R 165 0 R 166 0 R 167 0 R 144 0 R 169 0 R 171 0 R 172 0 R 173 0 R 175 0 R 176 0 R 177 0 R 180 0 R 144 0 R 182 0 R 186 0 R 194 0 R 195 0 R 199 0 R 200 0 R 202 0 R 203 0 R 204 0 R 205 0 R 206 0 R 207 0 R 208 0 R 209 0 R 210 0 R 211 0 R 212 0 R 213 0 R 194 0 R] +] >> +endobj +225 0 obj +<< /Limits [(ID.0002) (ID.0051)]/Names [(ID.0002) 21 0 R (ID.0003) 32 0 R (ID.0004) 33 0 R (ID.0005) 34 0 R (ID.0006) 35 0 R (ID.0007) 36 0 R (ID.0008) 37 0 R (ID.0009) 40 0 R (ID.0010) 41 0 R (ID.0011) 42 0 R (ID.0012) 43 0 R (ID.0013) 44 0 R (ID.0014) 45 0 R (ID.0015) 46 0 R (ID.0016) 47 0 R (ID.0017) 48 0 R (ID.0018) 49 0 R (ID.0019) 50 0 R (ID.0020) 51 0 R (ID.0021) 52 0 R (ID.0022) 53 0 R (ID.0023) 54 0 R (ID.0024) 55 0 R (ID.0025) 56 0 R (ID.0026) 57 0 R (ID.0027) 58 0 R (ID.0028) 59 0 R (ID.0029) 60 0 R (ID.0030) 61 0 R (ID.0031) 62 0 R (ID.0032) 63 0 R (ID.0033) 65 0 R (ID.0034) 66 0 R (ID.0035) 67 0 R (ID.0036) 68 0 R (ID.0037) 69 0 R (ID.0038) 70 0 R (ID.0039) 71 0 R (ID.0040) 73 0 R (ID.0041) 74 0 R (ID.0042) 76 0 R (ID.0043) 78 0 R (ID.0044) 80 0 R (ID.0045) 81 0 R (ID.0046) 82 0 R (ID.0047) 83 0 R (ID.0048) 84 0 R (ID.0049) 85 0 R (ID.0050) 86 0 R (ID.0051) 87 0 R ] >> +endobj +226 0 obj +<< /Limits [(ID.0052) (ID.0101)]/Names [(ID.0052) 88 0 R (ID.0053) 89 0 R (ID.0054) 90 0 R (ID.0055) 91 0 R (ID.0056) 92 0 R (ID.0057) 93 0 R (ID.0058) 95 0 R (ID.0059) 96 0 R (ID.0060) 99 0 R (ID.0061) 100 0 R (ID.0062) 101 0 R (ID.0063) 102 0 R (ID.0064) 103 0 R (ID.0065) 104 0 R (ID.0066) 106 0 R (ID.0067) 107 0 R (ID.0068) 109 0 R (ID.0069) 110 0 R (ID.0070) 111 0 R (ID.0071) 112 0 R (ID.0072) 113 0 R (ID.0073) 114 0 R (ID.0074) 115 0 R (ID.0075) 116 0 R (ID.0076) 117 0 R (ID.0077) 118 0 R (ID.0078) 119 0 R (ID.0079) 120 0 R (ID.0080) 121 0 R (ID.0081) 123 0 R (ID.0082) 124 0 R (ID.0083) 127 0 R (ID.0084) 128 0 R (ID.0085) 129 0 R (ID.0086) 131 0 R (ID.0087) 132 0 R (ID.0088) 133 0 R (ID.0089) 134 0 R (ID.0090) 135 0 R (ID.0091) 136 0 R (ID.0092) 137 0 R (ID.0093) 139 0 R (ID.0094) 140 0 R (ID.0095) 141 0 R (ID.0096) 143 0 R (ID.0097) 144 0 R (ID.0098) 147 0 R (ID.0099) 148 0 R (ID.0100) 149 0 R (ID.0101) 150 0 R ] >> +endobj +227 0 obj +<< /Limits [(ID.0102) (ID.0151)]/Names [(ID.0102) 151 0 R (ID.0103) 152 0 R (ID.0104) 153 0 R (ID.0105) 154 0 R (ID.0106) 155 0 R (ID.0107) 156 0 R (ID.0108) 157 0 R (ID.0109) 158 0 R (ID.0110) 159 0 R (ID.0111) 160 0 R (ID.0112) 161 0 R (ID.0113) 162 0 R (ID.0114) 163 0 R (ID.0115) 165 0 R (ID.0116) 166 0 R (ID.0117) 167 0 R (ID.0118) 168 0 R (ID.0119) 169 0 R (ID.0120) 171 0 R (ID.0121) 172 0 R (ID.0122) 173 0 R (ID.0123) 174 0 R (ID.0124) 175 0 R (ID.0125) 176 0 R (ID.0126) 177 0 R (ID.0127) 178 0 R (ID.0128) 179 0 R (ID.0129) 180 0 R (ID.0130) 181 0 R (ID.0131) 182 0 R (ID.0132) 183 0 R (ID.0133) 184 0 R (ID.0134) 185 0 R (ID.0135) 186 0 R (ID.0136) 187 0 R (ID.0137) 188 0 R (ID.0138) 190 0 R (ID.0139) 191 0 R (ID.0140) 192 0 R (ID.0141) 193 0 R (ID.0142) 194 0 R (ID.0143) 195 0 R (ID.0144) 198 0 R (ID.0145) 199 0 R (ID.0146) 200 0 R (ID.0147) 202 0 R (ID.0148) 203 0 R (ID.0149) 204 0 R (ID.0150) 205 0 R (ID.0151) 206 0 R ] >> +endobj +228 0 obj +<< /Limits [(ID.0152) (ID.0158)]/Names [(ID.0152) 207 0 R (ID.0153) 208 0 R (ID.0154) 209 0 R (ID.0155) 210 0 R (ID.0156) 211 0 R (ID.0157) 212 0 R (ID.0158) 213 0 R ] >> +endobj +229 0 obj +<< /Kids [225 0 R 226 0 R 227 0 R 228 0 R] >> +endobj +7 0 obj +<< /Artifact /NonStruct /DocumentFragment /Art /Aside /Note /H7 /H6 /H8 /H6 /H9 /H6 /H10 /H6 /Title /P /FENote /Note /Sub /Span /Em /Span /Strong /Span /title /P /part /P /section /H1 /subsection /H2 /subsubsection /H3 /paragraph /H4 /subparagraph /H5 /list /L /itemize /L /enumerate /L /description /L /quote /BlockQuote /quotation /BlockQuote /verbatim /P /item /LI /itemlabel /Lbl /itembody /LBody /footnote /Note /footnotemark /Lbl /footnotelabel /Lbl /text-unit /Part /text /P /theorem-like /Sect /codeline /Span /float /Note /figures /Sect /tables /Sect >> +endobj +230 0 obj +<< /justify <> +/display <> +/inline <> +/TH-both <> +/TH-row <> +/TH-col <> + >> +endobj +9 0 obj +<< /Type /Namespace /NS (http://iso.org/pdf/ssn) >> +endobj +11 0 obj +<< /Type /Namespace /NS (http://iso.org/pdf2/ssn) >> +endobj +13 0 obj +<< /Type /Namespace /NS (http://www.w3.org/1998/Math/MathML) >> +endobj +16 0 obj +<< /title [/Title 11 0 R] /part [/Title 11 0 R] /section [/H1 11 0 R] /subsection [/H2 11 0 R] /subsubsection [/H3 11 0 R] /paragraph [/H4 11 0 R] /subparagraph [/H5 11 0 R] /list [/L 11 0 R] /itemize [/L 11 0 R] /enumerate [/L 11 0 R] /description [/L 11 0 R] /quote [/BlockQuote 9 0 R] /quotation [/BlockQuote 9 0 R] /verbatim [/P 11 0 R] /item [/LI 11 0 R] /itemlabel [/Lbl 11 0 R] /itembody [/LBody 11 0 R] /footnote [/FENote 11 0 R] /footnotemark [/Lbl 11 0 R] /footnotelabel [/Lbl 11 0 R] /text-unit [/Part 11 0 R] /text [/P 11 0 R] /theorem-like [/Sect 11 0 R] /codeline [/Sub 11 0 R] /float [/Aside 11 0 R] /figures [/Sect 11 0 R] /tables [/Sect 11 0 R] >> +endobj +15 0 obj +<< /Type /Namespace /NS (https://www.latex-project.org/ns/dflt) /RoleMapNS 16 0 R >> +endobj +18 0 obj +<< /chapter [/H1 11 0 R] /section [/H2 11 0 R] /subsection [/H3 11 0 R] /subsubsection [/H4 11 0 R] /paragraph [/H5 11 0 R] /subparagraph [/H6 11 0 R] >> +endobj +17 0 obj +<< /Type /Namespace /NS (https://www.latex-project.org/ns/book) /RoleMapNS 18 0 R >> +endobj +19 0 obj +<< /Type /Namespace /NS (data:,C9B55C18-275C-494E-C10F-E4B83CD03F23) >> +endobj +8 0 obj +[ 9 0 R 11 0 R 13 0 R 15 0 R 17 0 R 19 0 R ] +endobj +21 0 obj +<< /Type /StructElem /S /Document /NS 11 0 R /P 5 0 R /K [34 0 R 36 0 R 95 0 R 123 0 R 143 0 R 193 0 R] /ID (ID.0002) >> +endobj +32 0 obj +<< /Type /StructElem /S /Artifact /NS 15 0 R /P 5 0 R /ID (ID.0003) >> +endobj +33 0 obj +<< /Type /StructElem /S /Artifact /NS 15 0 R /P 5 0 R /ID (ID.0004) >> +endobj +34 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 35 0 R /ID (ID.0005) >> +endobj +35 0 obj +<< /Type /StructElem /C /justify /S /text /NS 15 0 R /P 34 0 R /K <> /ID (ID.0006) >> +endobj +36 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 37 0 R /ID (ID.0007) >> +endobj +37 0 obj +<< /Type /StructElem /C /display /AF [23 0 R 39 0 R] /T /S /Formula /NS 11 0 R /P 36 0 R /K [<> <> <> 71 0 R] /ID (ID.0008) >> +endobj +40 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 81 0 R /K 41 0 R /ID (ID.0009) >> +endobj +41 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 40 0 R /K <> /ID (ID.0010) >> +endobj +42 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 81 0 R /K 43 0 R /ID (ID.0011) >> +endobj +43 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 42 0 R /K <> /ID (ID.0012) >> +endobj +44 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 81 0 R /K 45 0 R /ID (ID.0013) >> +endobj +45 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 44 0 R /K <> /ID (ID.0014) >> +endobj +46 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 82 0 R /K 47 0 R /ID (ID.0015) >> +endobj +47 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 46 0 R /K <> /ID (ID.0016) >> +endobj +48 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 82 0 R /K 49 0 R /ID (ID.0017) >> +endobj +49 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 48 0 R /K <> /ID (ID.0018) >> +endobj +50 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 82 0 R /K 51 0 R /ID (ID.0019) >> +endobj +51 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 50 0 R /K <> /ID (ID.0020) >> +endobj +52 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 83 0 R /K 53 0 R /ID (ID.0021) >> +endobj +53 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 52 0 R /K <> /ID (ID.0022) >> +endobj +54 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 83 0 R /K 55 0 R /ID (ID.0023) >> +endobj +55 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 54 0 R /K <> /ID (ID.0024) >> +endobj +56 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 83 0 R /K 57 0 R /ID (ID.0025) >> +endobj +57 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 56 0 R /K <> /ID (ID.0026) >> +endobj +58 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 91 0 R /K 59 0 R /ID (ID.0027) >> +endobj +59 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 58 0 R /K <> /ID (ID.0028) >> +endobj +60 0 obj +<< /Type /StructElem /S /mtext /NS 13 0 R /P 66 0 R /K [<> 61 0 R] /ID (ID.0029) >> +endobj +61 0 obj +<< /Type /StructElem /S /math /NS 13 0 R /P 60 0 R /K [62 0 R 63 0 R 65 0 R] /ID (ID.0030) >> +endobj +62 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 61 0 R /K <> /ID (ID.0031) >> +endobj +63 0 obj +<< /Type /StructElem /A 64 0 R /S /mo /NS 13 0 R /P 61 0 R /K <> /ID (ID.0032) >> +endobj +65 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 61 0 R /K <> /ID (ID.0033) >> +endobj +66 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 91 0 R /K 60 0 R /ID (ID.0034) >> +endobj +67 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 92 0 R /K 68 0 R /ID (ID.0035) >> +endobj +68 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 67 0 R /K <> /ID (ID.0036) >> +endobj +69 0 obj +<< /Type /StructElem /S /mtext /NS 13 0 R /P 70 0 R /K <> /ID (ID.0037) >> +endobj +70 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 92 0 R /K 69 0 R /ID (ID.0038) >> +endobj +71 0 obj +<< /Type /StructElem /A 72 0 R /S /math /NS 13 0 R /P 37 0 R /K [73 0 R 86 0 R 87 0 R] /ID (ID.0039) >> +endobj +73 0 obj +<< /Type /StructElem /S /mrow /NS 13 0 R /P 71 0 R /K [74 0 R 76 0 R 78 0 R 84 0 R 85 0 R] /ID (ID.0040) >> +endobj +74 0 obj +<< /Type /StructElem /A 75 0 R /S /mo /NS 13 0 R /P 73 0 R /K <> /ID (ID.0041) >> +endobj +76 0 obj +<< /Type /StructElem /A 77 0 R /S /mspace /NS 13 0 R /P 73 0 R /ID (ID.0042) >> +endobj +78 0 obj +<< /Type /StructElem /A 79 0 R /S /mpadded /NS 13 0 R /P 73 0 R /K 80 0 R /ID (ID.0043) >> +endobj +80 0 obj +<< /Type /StructElem /S /mtable /NS 13 0 R /P 78 0 R /K [81 0 R 82 0 R 83 0 R] /ID (ID.0044) >> +endobj +81 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 80 0 R /K [40 0 R 42 0 R 44 0 R] /ID (ID.0045) >> +endobj +82 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 80 0 R /K [46 0 R 48 0 R 50 0 R] /ID (ID.0046) >> +endobj +83 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 80 0 R /K [52 0 R 54 0 R 56 0 R] /ID (ID.0047) >> +endobj +84 0 obj +<< /Type /StructElem /A 77 0 R /S /mspace /NS 13 0 R /P 73 0 R /ID (ID.0048) >> +endobj +85 0 obj +<< /Type /StructElem /A 75 0 R /S /mo /NS 13 0 R /P 73 0 R /K <> /ID (ID.0049) >> +endobj +86 0 obj +<< /Type /StructElem /A 64 0 R /S /mo /NS 13 0 R /P 71 0 R /K <> /ID (ID.0050) >> +endobj +87 0 obj +<< /Type /StructElem /S /mrow /NS 13 0 R /P 71 0 R /K [88 0 R 89 0 R 93 0 R] /ID (ID.0051) >> +endobj +88 0 obj +<< /Type /StructElem /A 75 0 R /S /mo /NS 13 0 R /P 87 0 R /K <> /ID (ID.0052) >> +endobj +89 0 obj +<< /Type /StructElem /A 79 0 R /S /mpadded /NS 13 0 R /P 87 0 R /K 90 0 R /ID (ID.0053) >> +endobj +90 0 obj +<< /Type /StructElem /S /mtable /NS 13 0 R /P 89 0 R /K [91 0 R 92 0 R] /ID (ID.0054) >> +endobj +91 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 90 0 R /K [58 0 R 66 0 R] /ID (ID.0055) >> +endobj +92 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 90 0 R /K [67 0 R 70 0 R] /ID (ID.0056) >> +endobj +93 0 obj +<< /Type /StructElem /A 94 0 R /S /mspace /NS 13 0 R /P 87 0 R /ID (ID.0057) >> +endobj +95 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 96 0 R /ID (ID.0058) >> +endobj +96 0 obj +<< /Type /StructElem /C /display /AF [25 0 R 98 0 R] /T /S /Formula /NS 11 0 R /P 95 0 R /K [<> <> 99 0 R] /ID (ID.0059) >> +endobj +99 0 obj +<< /Type /StructElem /A 72 0 R /S /math /NS 13 0 R /P 96 0 R /K [100 0 R 101 0 R 102 0 R 121 0 R] /ID (ID.0060) >> +endobj +100 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 99 0 R /K <> /ID (ID.0061) >> +endobj +101 0 obj +<< /Type /StructElem /A 64 0 R /S /mo /NS 13 0 R /P 99 0 R /K <> /ID (ID.0062) >> +endobj +102 0 obj +<< /Type /StructElem /S /mfrac /NS 13 0 R /P 99 0 R /K [103 0 R 118 0 R] /ID (ID.0063) >> +endobj +103 0 obj +<< /Type /StructElem /S /mrow /NS 13 0 R /P 102 0 R /K [104 0 R 106 0 R 107 0 R 109 0 R] /ID (ID.0064) >> +endobj +104 0 obj +<< /Type /StructElem /A 105 0 R /S /mo /NS 13 0 R /P 103 0 R /K <> /ID (ID.0065) >> +endobj +106 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 103 0 R /K <> /ID (ID.0066) >> +endobj +107 0 obj +<< /Type /StructElem /A 108 0 R /S /mo /NS 13 0 R /P 103 0 R /K <> /ID (ID.0067) >> +endobj +109 0 obj +<< /Type /StructElem /S /msqrt /NS 13 0 R /P 103 0 R /K 110 0 R /ID (ID.0068) >> +endobj +110 0 obj +<< /Type /StructElem /S /mrow /NS 13 0 R /P 109 0 R /K [111 0 R 114 0 R 115 0 R 116 0 R 117 0 R] /ID (ID.0069) >> +endobj +111 0 obj +<< /Type /StructElem /S /msup /NS 13 0 R /P 110 0 R /K [112 0 R 113 0 R] /ID (ID.0070) >> +endobj +112 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 111 0 R /K <> /ID (ID.0071) >> +endobj +113 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 111 0 R /K <> /ID (ID.0072) >> +endobj +114 0 obj +<< /Type /StructElem /A 108 0 R /S /mo /NS 13 0 R /P 110 0 R /K <> /ID (ID.0073) >> +endobj +115 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 110 0 R /K <> /ID (ID.0074) >> +endobj +116 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 110 0 R /K <> /ID (ID.0075) >> +endobj +117 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 110 0 R /K <> /ID (ID.0076) >> +endobj +118 0 obj +<< /Type /StructElem /S /mrow /NS 13 0 R /P 102 0 R /K [119 0 R 120 0 R] /ID (ID.0077) >> +endobj +119 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 118 0 R /K <> /ID (ID.0078) >> +endobj +120 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 118 0 R /K <> /ID (ID.0079) >> +endobj +121 0 obj +<< /Type /StructElem /A 122 0 R /S /mo /NS 13 0 R /P 99 0 R /K <> /ID (ID.0080) >> +endobj +123 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 124 0 R /ID (ID.0081) >> +endobj +124 0 obj +<< /Type /StructElem /C /display /AF [27 0 R 126 0 R] /T /S /Formula /NS 11 0 R /P 123 0 R /K [<> 127 0 R] /ID (ID.0082) >> +endobj +127 0 obj +<< /Type /StructElem /A 72 0 R /S /math /NS 13 0 R /P 124 0 R /K [128 0 R 132 0 R 135 0 R 139 0 R] /ID (ID.0083) >> +endobj +128 0 obj +<< /Type /StructElem /S /munder /NS 13 0 R /P 127 0 R /K [129 0 R 131 0 R] /ID (ID.0084) >> +endobj +129 0 obj +<< /Type /StructElem /A 130 0 R /S /mo /NS 13 0 R /P 128 0 R /K <> /ID (ID.0085) >> +endobj +131 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 128 0 R /K <> /ID (ID.0086) >> +endobj +132 0 obj +<< /Type /StructElem /S /munder /NS 13 0 R /P 127 0 R /K [133 0 R 134 0 R] /ID (ID.0087) >> +endobj +133 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 132 0 R /K <> /ID (ID.0088) >> +endobj +134 0 obj +<< /Type /StructElem /S /mo /NS 13 0 R /P 132 0 R /ID (ID.0089) >> +endobj +135 0 obj +<< /Type /StructElem /S /mover /NS 13 0 R /P 127 0 R /K [136 0 R 137 0 R] /ID (ID.0090) >> +endobj +136 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 135 0 R /K <> /ID (ID.0091) >> +endobj +137 0 obj +<< /Type /StructElem /A 138 0 R /S /mo /NS 13 0 R /P 135 0 R /K <> /ID (ID.0092) >> +endobj +139 0 obj +<< /Type /StructElem /S /msup /NS 13 0 R /P 127 0 R /K [140 0 R 141 0 R] /ID (ID.0093) >> +endobj +140 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 139 0 R /K <> /ID (ID.0094) >> +endobj +141 0 obj +<< /Type /StructElem /A 142 0 R /S /mi /NS 13 0 R /P 139 0 R /K <> /ID (ID.0095) >> +endobj +143 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 144 0 R /ID (ID.0096) >> +endobj +144 0 obj +<< /Type /StructElem /C /display /AF [29 0 R 146 0 R] /T /S /Formula /NS 11 0 R /P 143 0 R /K [<> 167 0 R <> 180 0 R <> 186 0 R 187 0 R] /ID (ID.0097) >> +endobj +147 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 190 0 R /K [148 0 R 149 0 R 150 0 R] /ID (ID.0098) >> +endobj +148 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 147 0 R /K <> /ID (ID.0099) >> +endobj +149 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 147 0 R /K <> /ID (ID.0100) >> +endobj +150 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 147 0 R /K <> /ID (ID.0101) >> +endobj +151 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 190 0 R /K [152 0 R 153 0 R 154 0 R 155 0 R] /ID (ID.0102) >> +endobj +152 0 obj +<< /Type /StructElem /A 64 0 R /S /mo /NS 13 0 R /P 151 0 R /K <> /ID (ID.0103) >> +endobj +153 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 151 0 R /K <> /ID (ID.0104) >> +endobj +154 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 151 0 R /K <> /ID (ID.0105) >> +endobj +155 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 151 0 R /K <> /ID (ID.0106) >> +endobj +156 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 190 0 R /K 157 0 R /ID (ID.0107) >> +endobj +157 0 obj +<< /Type /StructElem /S /msup /NS 13 0 R /P 156 0 R /K [158 0 R 159 0 R] /ID (ID.0108) >> +endobj +158 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 157 0 R /K <> /ID (ID.0109) >> +endobj +159 0 obj +<< /Type /StructElem /S /mrow /NS 13 0 R /P 157 0 R /K [160 0 R 161 0 R] /ID (ID.0110) >> +endobj +160 0 obj +<< /Type /StructElem /A 142 0 R /S /mi /NS 13 0 R /P 159 0 R /K <> /ID (ID.0111) >> +endobj +161 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 159 0 R /K <> /ID (ID.0112) >> +endobj +162 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 190 0 R /K [163 0 R 165 0 R 166 0 R] /ID (ID.0113) >> +endobj +163 0 obj +<< /Type /StructElem /A 164 0 R /S /mo /NS 13 0 R /P 162 0 R /K <> /ID (ID.0114) >> +endobj +165 0 obj +<< /Type /StructElem /A 164 0 R /S /mo /NS 13 0 R /P 162 0 R /K <> /ID (ID.0115) >> +endobj +166 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 162 0 R /K <> /ID (ID.0116) >> +endobj +167 0 obj +<< /Type /StructElem /S /Lbl /NS 11 0 R /P 144 0 R /K <> /ID (ID.0117) >> +endobj +168 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 191 0 R /K [169 0 R 171 0 R 172 0 R 173 0 R] /ID (ID.0118) >> +endobj +169 0 obj +<< /Type /StructElem /A 170 0 R /S /mo /NS 13 0 R /P 168 0 R /K <> /ID (ID.0119) >> +endobj +171 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 168 0 R /K <> /ID (ID.0120) >> +endobj +172 0 obj +<< /Type /StructElem /A 108 0 R /S /mo /NS 13 0 R /P 168 0 R /K <> /ID (ID.0121) >> +endobj +173 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 168 0 R /K <> /ID (ID.0122) >> +endobj +174 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 191 0 R /K [175 0 R 176 0 R 177 0 R] /ID (ID.0123) >> +endobj +175 0 obj +<< /Type /StructElem /A 64 0 R /S /mo /NS 13 0 R /P 174 0 R /K <> /ID (ID.0124) >> +endobj +176 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 174 0 R /K <> /ID (ID.0125) >> +endobj +177 0 obj +<< /Type /StructElem /A 170 0 R /S /mo /NS 13 0 R /P 174 0 R /K <> /ID (ID.0126) >> +endobj +178 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 191 0 R /ID (ID.0127) >> +endobj +179 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 191 0 R /ID (ID.0128) >> +endobj +180 0 obj +<< /Type /StructElem /S /Lbl /NS 11 0 R /P 144 0 R /K <> /ID (ID.0129) >> +endobj +181 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 192 0 R /K 182 0 R /ID (ID.0130) >> +endobj +182 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 181 0 R /K <> /ID (ID.0131) >> +endobj +183 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 192 0 R /ID (ID.0132) >> +endobj +184 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 192 0 R /ID (ID.0133) >> +endobj +185 0 obj +<< /Type /StructElem /S /mtd /NS 13 0 R /P 192 0 R /ID (ID.0134) >> +endobj +186 0 obj +<< /Type /StructElem /S /Lbl /NS 11 0 R /P 144 0 R /K <> /ID (ID.0135) >> +endobj +187 0 obj +<< /Type /StructElem /A 72 0 R /S /math /NS 13 0 R /P 144 0 R /K 188 0 R /ID (ID.0136) >> +endobj +188 0 obj +<< /Type /StructElem /A 189 0 R /S /mtable /NS 13 0 R /P 187 0 R /K [190 0 R 191 0 R 192 0 R] /ID (ID.0137) >> +endobj +190 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 188 0 R /K [147 0 R 151 0 R 156 0 R 162 0 R] /ID (ID.0138) >> +endobj +191 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 188 0 R /K [168 0 R 174 0 R 178 0 R 179 0 R] /ID (ID.0139) >> +endobj +192 0 obj +<< /Type /StructElem /S /mtr /NS 13 0 R /P 188 0 R /K [181 0 R 183 0 R 184 0 R 185 0 R] /ID (ID.0140) >> +endobj +193 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 194 0 R /ID (ID.0141) >> +endobj +194 0 obj +<< /Type /StructElem /C /justify /S /text /NS 15 0 R /P 193 0 R /K [<> 195 0 R <> ] /ID (ID.0142) >> +endobj +195 0 obj +<< /Type /StructElem /C /inline /AF [31 0 R 197 0 R] /T /S /Formula /NS 11 0 R /P 194 0 R /K [<> 198 0 R] /ID (ID.0143) >> +endobj +198 0 obj +<< /Type /StructElem /S /math /NS 13 0 R /P 195 0 R /K [199 0 R 200 0 R 202 0 R 203 0 R 204 0 R 205 0 R 206 0 R 207 0 R 208 0 R 209 0 R 210 0 R 211 0 R 212 0 R 213 0 R] /ID (ID.0144) >> +endobj +199 0 obj +<< /Type /StructElem /A 142 0 R /S /mi /NS 13 0 R /P 198 0 R /K <> /ID (ID.0145) >> +endobj +200 0 obj +<< /Type /StructElem /A 201 0 R /S /mo /NS 13 0 R /P 198 0 R /K <> /ID (ID.0146) >> +endobj +202 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 198 0 R /K <> /ID (ID.0147) >> +endobj +203 0 obj +<< /Type /StructElem /A 201 0 R /S /mo /NS 13 0 R /P 198 0 R /K <> /ID (ID.0148) >> +endobj +204 0 obj +<< /Type /StructElem /A 108 0 R /S /mo /NS 13 0 R /P 198 0 R /K <> /ID (ID.0149) >> +endobj +205 0 obj +<< /Type /StructElem /A 142 0 R /S /mi /NS 13 0 R /P 198 0 R /K <> /ID (ID.0150) >> +endobj +206 0 obj +<< /Type /StructElem /A 201 0 R /S /mo /NS 13 0 R /P 198 0 R /K <> /ID (ID.0151) >> +endobj +207 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 198 0 R /K <> /ID (ID.0152) >> +endobj +208 0 obj +<< /Type /StructElem /A 108 0 R /S /mo /NS 13 0 R /P 198 0 R /K <> /ID (ID.0153) >> +endobj +209 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 198 0 R /K <> /ID (ID.0154) >> +endobj +210 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 198 0 R /K <> /ID (ID.0155) >> +endobj +211 0 obj +<< /Type /StructElem /A 201 0 R /S /mo /NS 13 0 R /P 198 0 R /K <> /ID (ID.0156) >> +endobj +212 0 obj +<< /Type /StructElem /A 64 0 R /S /mo /NS 13 0 R /P 198 0 R /K <> /ID (ID.0157) >> +endobj +213 0 obj +<< /Type /StructElem /S /mn /NS 13 0 R /P 198 0 R /K <> /ID (ID.0158) >> +endobj +5 0 obj +<< /Type /StructTreeRoot /Namespaces 8 0 R /IDTree 229 0 R /ClassMap 230 0 R /ParentTree 6 0 R /RoleMap 7 0 R /K 21 0 R >> +endobj +231 0 obj +[ 66 [ 323 ] ] +endobj +233 0 obj +<< /Subtype /CIDFontType0C /Length 592 >> +[BINARY STREAM] +endobj +232 0 obj +<< /Type /FontDescriptor /FontName /QTQISS+LMRoman7-Regular /Flags 4 /FontBBox [ -483 -292 1562 1124 ] /Ascent 1124 /CapHeight 683 /Descent -292 /ItalicAngle 0 /StemV 108 /XHeight 431 /FontFile3 233 0 R >> +endobj +234 0 obj +<< /Length 687 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-QTQISS-LMRoman7-Regular-0) +%%Title: (TeX-QTQISS-LMRoman7-Regular-0 TeX QTQISS-LMRoman7-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (QTQISS-LMRoman7-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-QTQISS-LMRoman7-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +1 beginbfchar +<0042> <0069> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +221 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /QTQISS+LMRoman7-Regular /DescendantFonts [ 235 0 R ] /ToUnicode 234 0 R >> +endobj +235 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /QTQISS+LMRoman7-Regular /FontDescriptor 232 0 R /W 231 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +236 0 obj +[ 1013 [ 569 ] 1404 [ 620 ] 2981 [ 407 ] 4558 [ 668 ] ] +endobj +238 0 obj +<< /Subtype /CIDFontType0C /Length 1102 >> +[BINARY STREAM] +endobj +237 0 obj +<< /Type /FontDescriptor /FontName /ZIIHRW+LatinModernMath-Regular /Flags 4 /FontBBox [ -1042 -3060 4082 3560 ] /Ascent 806 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 113 /XHeight 431 /FontFile3 238 0 R >> +endobj +239 0 obj +<< /Length 772 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-ZIIHRW-LatinModernMath-Regular-0) +%%Title: (TeX-ZIIHRW-LatinModernMath-Regular-0 TeX ZIIHRW-LatinModernMath-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (ZIIHRW-LatinModernMath-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-ZIIHRW-LatinModernMath-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +4 beginbfchar +<03F5> <0032> +<057C> +<0BA5> <2032> +<11CE> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +220 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /ZIIHRW+LatinModernMath-Regular /DescendantFonts [ 240 0 R ] /ToUnicode 239 0 R >> +endobj +240 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /ZIIHRW+LatinModernMath-Regular /FontDescriptor 237 0 R /W 236 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +241 0 obj +[ 9 [ 389 389 ] 12 [ 778 ] 15 [ 278 ] 17 [ 500 500 500 500 500 500 ] 30 [ 778 ] 1296 [ 529 429 433 520 466 490 ] 1319 [ 572 ] 1790 [ 0 ] 2455 [ 663 663 ] 2503 [ 875 875 875 875 875 875 ] 2515 [ 902 ] 2615 [ 778 ] 2619 [ 778 ] 3074 [ 1444 ] 3077 [ 833 ] 4474 [ 570 ] ] +endobj +243 0 obj +<< /Subtype /CIDFontType0C /Length 4088 >> +[BINARY STREAM] +endobj +242 0 obj +<< /Type /FontDescriptor /FontName /RHJXIX+LatinModernMath-Regular /Flags 4 /FontBBox [ -1042 -3060 4082 3560 ] /Ascent 806 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 93 /XHeight 431 /FontFile3 243 0 R >> +endobj +244 0 obj +<< /Length 1203 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-RHJXIX-LatinModernMath-Regular-0) +%%Title: (TeX-RHJXIX-LatinModernMath-Regular-0 TeX RHJXIX-LatinModernMath-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (RHJXIX-LatinModernMath-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-RHJXIX-LatinModernMath-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +33 beginbfchar +<0009> <0028> +<000A> <0029> +<000C> <002B> +<000F> <002E> +<0011> <0030> +<0012> <0031> +<0013> <0032> +<0014> <0033> +<0015> <0034> +<0016> <0035> +<001E> <003D> +<0510> +<0511> +<0512> +<0513> +<0514> +<0515> +<0527> +<06FE> <0307> +<0997> <0028> +<0998> <0029> +<09C7> <239D> +<09C8> <239C> +<09C9> <239B> +<09CA> <23A0> +<09CB> <239F> +<09CC> <239E> +<09D3> <007B> +<0A37> <2212> +<0A3B> <00B1> +<0C02> <2211> +<0C05> <221A> +<117A> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +219 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /RHJXIX+LatinModernMath-Regular /DescendantFonts [ 245 0 R ] /ToUnicode 244 0 R >> +endobj +245 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /RHJXIX+LatinModernMath-Regular /FontDescriptor 242 0 R /W 241 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +246 0 obj +[ 49 [ 681 444 ] 55 [ 306 ] 59 [ 500 ] 63 [ 556 ] 66 [ 278 ] 72 [ 278 ] 77 [ 556 ] 81 [ 500 500 ] 85 [ 389 389 ] 88 [ 278 ] 98 [ 394 ] 103 [ 333 ] 105 [ 389 500 500 ] ] +endobj +248 0 obj +<< /Subtype /CIDFontType0C /Length 2411 >> +[BINARY STREAM] +endobj +247 0 obj +<< /Type /FontDescriptor /FontName /RYKTJL+LMRoman10-Regular /Flags 4 /FontBBox [ -430 -290 1417 1127 ] /Ascent 1127 /CapHeight 683 /Descent -290 /ItalicAngle 0 /StemV 93 /XHeight 431 /FontFile3 248 0 R >> +endobj +249 0 obj +<< /Length 931 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-RYKTJL-LMRoman10-Regular-0) +%%Title: (TeX-RYKTJL-LMRoman10-Regular-0 TeX RYKTJL-LMRoman10-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (RYKTJL-LMRoman10-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-RYKTJL-LMRoman10-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +18 beginbfchar +<0031> <0045> +<0032> <0065> +<0037> <0066> +<003B> <0067> +<003F> <0068> +<0042> <0069> +<0048> <006C> +<004D> <006E> +<0051> <006F> +<0052> <0031> +<0055> <0028> +<0056> <0029> +<0058> <002E> +<0062> <0073> +<0067> <0020> +<0069> <0074> +<006A> <0033> +<006B> <0032> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +218 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /RYKTJL+LMRoman10-Regular /DescendantFonts [ 250 0 R ] /ToUnicode 249 0 R >> +endobj +250 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /RYKTJL+LMRoman10-Regular /FontDescriptor 247 0 R /W 246 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +222 0 obj +<< /Type /Pages /Count 1 /Kids [ 216 0 R ] >> +endobj +251 0 obj +<< /EmbeddedFiles 224 0 R >> +endobj +252 0 obj +<< /Type /Catalog /Pages 222 0 R /Names 251 0 R /MarkInfo 223 0 R/Lang (en)/Metadata 214 0 R/StructTreeRoot 5 0 R >> +endobj +253 0 obj +<< /Producer (LuaTeX)/Creator (TeX)/CreationDate (D:20160520090000Z)/ModDate (D:20160520090000Z) /Trapped /False >> +endobj +xref +0 254 +0000000002 65535 f +0000028308 00000 n +0000000003 00000 f +0000000004 00000 f +0000000010 00000 f +0000054101 00000 n +0000028513 00000 n +0000032337 00000 n +0000034522 00000 n +0000033157 00000 n +0000000012 00000 f +0000033225 00000 n +0000000014 00000 f +0000033295 00000 n +0000000020 00000 f +0000034058 00000 n +0000033376 00000 n +0000034331 00000 n +0000034160 00000 n +0000034433 00000 n +0000000000 00000 f +0000034583 00000 n +0000000020 00000 n +0000000299 00000 n +0000000486 00000 n +0000001131 00000 n +0000001318 00000 n +0000001800 00000 n +0000001987 00000 n +0000003339 00000 n +0000003526 00000 n +0000004205 00000 n +0000034722 00000 n +0000034811 00000 n +0000034900 00000 n +0000035001 00000 n +0000035138 00000 n +0000035239 00000 n +0000004392 00000 n +0000004707 00000 n +0000035525 00000 n +0000035620 00000 n +0000035743 00000 n +0000035838 00000 n +0000035961 00000 n +0000036056 00000 n +0000036179 00000 n +0000036274 00000 n +0000036397 00000 n +0000036492 00000 n +0000036615 00000 n +0000036710 00000 n +0000036833 00000 n +0000036928 00000 n +0000037052 00000 n +0000037147 00000 n +0000037271 00000 n +0000037366 00000 n +0000037490 00000 n +0000037585 00000 n +0000037709 00000 n +0000037845 00000 n +0000037957 00000 n +0000038081 00000 n +0000004907 00000 n +0000038216 00000 n +0000038340 00000 n +0000038435 00000 n +0000038530 00000 n +0000038654 00000 n +0000038781 00000 n +0000038876 00000 n +0000004978 00000 n +0000038999 00000 n +0000039125 00000 n +0000005032 00000 n +0000039259 00000 n +0000005119 00000 n +0000039358 00000 n +0000005174 00000 n +0000039468 00000 n +0000039582 00000 n +0000039693 00000 n +0000039804 00000 n +0000039915 00000 n +0000040014 00000 n +0000040149 00000 n +0000040284 00000 n +0000040396 00000 n +0000040531 00000 n +0000040641 00000 n +0000040748 00000 n +0000040852 00000 n +0000040956 00000 n +0000005246 00000 n +0000041055 00000 n +0000041156 00000 n +0000005300 00000 n +0000005516 00000 n +0000041408 00000 n +0000041542 00000 n +0000041667 00000 n +0000041803 00000 n +0000041912 00000 n +0000042037 00000 n +0000005716 00000 n +0000042175 00000 n +0000042301 00000 n +0000005782 00000 n +0000042439 00000 n +0000042539 00000 n +0000042672 00000 n +0000042781 00000 n +0000042907 00000 n +0000043033 00000 n +0000043171 00000 n +0000043297 00000 n +0000043423 00000 n +0000043549 00000 n +0000043658 00000 n +0000043784 00000 n +0000043910 00000 n +0000005854 00000 n +0000044047 00000 n +0000044150 00000 n +0000005914 00000 n +0000006119 00000 n +0000044370 00000 n +0000044506 00000 n +0000044617 00000 n +0000006322 00000 n +0000044755 00000 n +0000044881 00000 n +0000044992 00000 n +0000045118 00000 n +0000045204 00000 n +0000045314 00000 n +0000045440 00000 n +0000006408 00000 n +0000045578 00000 n +0000045687 00000 n +0000045813 00000 n +0000006463 00000 n +0000045951 00000 n +0000046054 00000 n +0000006523 00000 n +0000006752 00000 n +0000046357 00000 n +0000046473 00000 n +0000046599 00000 n +0000046725 00000 n +0000046851 00000 n +0000046975 00000 n +0000047112 00000 n +0000047238 00000 n +0000047364 00000 n +0000047490 00000 n +0000047588 00000 n +0000047697 00000 n +0000047823 00000 n +0000047932 00000 n +0000048070 00000 n +0000048196 00000 n +0000048312 00000 n +0000006955 00000 n +0000048450 00000 n +0000048588 00000 n +0000048714 00000 n +0000048841 00000 n +0000048965 00000 n +0000007021 00000 n +0000049103 00000 n +0000049229 00000 n +0000049367 00000 n +0000049493 00000 n +0000049609 00000 n +0000049746 00000 n +0000049872 00000 n +0000050010 00000 n +0000050097 00000 n +0000050184 00000 n +0000050311 00000 n +0000050409 00000 n +0000050535 00000 n +0000050622 00000 n +0000050709 00000 n +0000050796 00000 n +0000050923 00000 n +0000051033 00000 n +0000007145 00000 n +0000051164 00000 n +0000051288 00000 n +0000051412 00000 n +0000051536 00000 n +0000051639 00000 n +0000051826 00000 n +0000007312 00000 n +0000007483 00000 n +0000052025 00000 n +0000052230 00000 n +0000052368 00000 n +0000007686 00000 n +0000052506 00000 n +0000052632 00000 n +0000052770 00000 n +0000052908 00000 n +0000053046 00000 n +0000053184 00000 n +0000053310 00000 n +0000053448 00000 n +0000053574 00000 n +0000053700 00000 n +0000053838 00000 n +0000053975 00000 n +0000007762 00000 n +0000028204 00000 n +0000028065 00000 n +0000019546 00000 n +0000069195 00000 n +0000064926 00000 n +0000058602 00000 n +0000055920 00000 n +0000069555 00000 n +0000028355 00000 n +0000028392 00000 n +0000029259 00000 n +0000030171 00000 n +0000031124 00000 n +0000032086 00000 n +0000032274 00000 n +0000032916 00000 n +0000054240 00000 n +0000054949 00000 n +0000054272 00000 n +0000055172 00000 n +0000056076 00000 n +0000056278 00000 n +0000057538 00000 n +0000056351 00000 n +0000057769 00000 n +0000058765 00000 n +0000058974 00000 n +0000063432 00000 n +0000059259 00000 n +0000063662 00000 n +0000065089 00000 n +0000065298 00000 n +0000067980 00000 n +0000065484 00000 n +0000068203 00000 n +0000069352 00000 n +0000069619 00000 n +0000069665 00000 n +0000069799 00000 n +trailer +<< /Size 254 /Root 252 0 R /Info 253 0 R /ID [ <2350CAD05F8A7AF0AA4058486855344F> <2350CAD05F8A7AF0AA4058486855344F> ] >> +startxref +69932 +%%EOF diff --git a/testfiles-lua/test_structnum.pvt b/testfiles-lua/test_structnum.pvt new file mode 100644 index 0000000..3a119b0 --- /dev/null +++ b/testfiles-lua/test_structnum.pvt @@ -0,0 +1,35 @@ +\ExplSyntaxOn +\sys_gset_rand_seed:n{42} +\ExplSyntaxOff +\DocumentMetadata{ + lang=en, + testphase={phase-III,math}, + pdfversion=2.0, + pdfstandard=ua-2, + pdfstandard=a-4f, + uncompress +} +\input{regression-test} +\documentclass{article} + +\usepackage{unicode-math} + +% suppress writing of luamml-mathml +\tagpdfsetup{math/mathml/luamml/write=false} % + +% suppress mathml-AF reading +\tagpdfsetup{math/mathml/sources=} % +\tagpdfsetup{math/mathml/AF=false,attach-css=false} +\begin{document} +\ExplSyntaxOn + +\luamml_structelem: +$a = b \quad + \tagstructbegin{tag=mtext,stash}\tagmcbegin{} + \luamml_annotate:en{nucleus=true,structnum=\tag_get:n{struct_num}} + {\mbox{some~text~with~\emph{structure}}} + \tagmcend\tagstructend +$ +\ExplSyntaxOff +\end{document} + diff --git a/testfiles-lua/test_structnum.tpf b/testfiles-lua/test_structnum.tpf new file mode 100644 index 0000000..57f1ccb --- /dev/null +++ b/testfiles-lua/test_structnum.tpf @@ -0,0 +1,783 @@ +%PDF-2.0 +% +22 0 obj +<< /N 3 /Length 3268 >> +[BINARY STREAM] +endobj +23 0 obj +<< /Type /OutputIntent /S /GTS_PDFA1 /DestOutputProfile 22 0 R /OutputConditionIdentifier (IEC\040sRGB) /Info (IEC\04061966-2.1\040Default\040RGB\040colour\040space\040-\040sRGB) /RegistryName (http://www.iec.ch) >> +endobj +29 0 obj +<< /Subtype /application#2Fx-tex/Type /EmbeddedFile /Params<> /Length 192 >> +stream +$a=b\quad \tagstructbegin {tag=mtext,stash}\tagmcbegin {}\luamml_annotate:en {nucleus=true,structnum=\tag_get:n {struct_num}}{\mbox {some text with \emph {structure}}}\tagmcend \tagstructend $ +endstream +endobj +30 0 obj +<< /Type /Filespec /AFRelationship /Source /Desc (TeX source) /F (tag-AFfile1.tex) /UF /EF<> >> +endobj +36 0 obj +<< /O/NSO/NS 13 0 R/lspace(0.278em)/rspace(0.278em) >> +endobj +39 0 obj +<< /O/NSO/NS 13 0 R/width(9.963pt) >> +endobj +40 0 obj +<< /Type /Metadata /Subtype /XML /Length 16669 >> +stream + + + + + + + + XMP Media Management Schema + xmpMM + http://ns.adobe.com/xap/1.0/mm/ + + + + OriginalDocumentID + URI + internal + The common identifier for all versions and renditions of a document. + + + + + + PDF/A Identification Schema + pdfaid + http://www.aiim.org/pdfa/ns/id/ + + + + year + Integer + internal + Year of standard + + + rev + Integer + internal + Revision year of standard + + + + + + PDF/UA Universal Accessibility Schema + pdfuaid + http://www.aiim.org/pdfua/ns/id/ + + + + part + Integer + internal + Part of ISO 14289 standard + + + rev + Integer + internal + Revision of ISO 14289 standard + + + + + + PDF/X ID Schema + pdfxid + http://www.npes.org/pdfx/ns/id/ + + + + GTS_PDFXVersion + Text + internal + ID of PDF/X standard + + + + + + PRISM Basic Metadata + prism + http://prismstandard.org/namespaces/basic/3.0/ + + + + complianceProfile + Text + internal + PRISM specification compliance profile to which this document adheres + + + publicationName + Text + external + Publication name + + + aggregationType + Text + external + Publication type + + + bookEdition + Text + external + Edition of the book in which the document was published + + + volume + Text + external + Publication volume number + + + number + Text + external + Publication issue number within a volume + + + pageRange + Text + external + Page range for the document within the print version of its publication + + + issn + Text + external + ISSN for the printed publication in which the document was published + + + eIssn + Text + external + ISSN for the electronic publication in which the document was published + + + isbn + Text + external + ISBN for the publication in which the document was published + + + doi + Text + external + Digital Object Identifier for the document + + + url + URL + external + URL at which the document can be found + + + byteCount + Integer + internal + Approximate file size in octets + + + pageCount + Integer + internal + Number of pages in the print version of the document + + + subtitle + Text + external + Document's subtitle + + + + + + PDF Declarations Schema + pdfd + http://pdfa.org/declarations/ + + + + declarations + Bag declaration + external + An unordered array of PDF Declaration entries, where each PDF Declaration representing a statement of conformance with an identified external standard or profile, along with optional information identifying the nature of the claim. + + + + + + + claim + http://pdfa.org/declarations/ + pdfd + A structure describing properties of an individualclaim. + + + + claimReport + Text + A URL to a report containing details of the specific conformance claim. + + + claimCredentials + Text + The claimant's credentials. + + + claimDate + Text + A date identifying when the claim was made. + + + claimBy + Text + The name of the organization and/or individual and/or software making the claim. + + + + + + declaration + http://pdfa.org/declarations/ + pdfd + A structure describing a single PDF Declaration asserting conformance with an externally-identified standard or profile. + + + + conformsTo + Text + A property containing a URI specifying the standard or profile by the PDF Declaration. This property is intended to mirror the Dublin Core property dc:conformsTo. + + + claimData + Bag claim + An unordered array of claim data, where each claim identifies the nature of the claim. + + + + + + + + + + luahbtex-NN.NN.NN + 2.0 + 4 + F + 2020 + 2 + 2024 + + + + http://pdfa.org/declarations/wtpdf#accessibility1.0 + + + + LaTeX Project + 2016-05-20 + + + + + + http://pdfa.org/declarations/wtpdf#reuse1.0 + + + + LaTeX Project + 2016-05-20 + + + + + + + + + Text + + + + + en + + + + + 2016-05-20T09:00:00Z + + + application/pdf + test_structnum.tex + LaTeX + 2016-05-20T09:00:00Z + 2016-05-20T09:00:00Z + 2016-05-20T09:00:00Z + uuid:2b6fecb9-b74a-4265-883d-138c87e7152f + uuid:0a57c455-157a-4141-8c19-6237d832fc80 + three + 1 + + + + +endstream +endobj +43 0 obj +<< /Length 653 >> +stream +/opacity1 gs +/Artifact BMC +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 148.712 657.235 Tm [<0510>]TJ +ET +EMC +/mo<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 156.75 657.235 Tm [<001E>]TJ +ET +EMC +/mi<> BDC +BT +/F20 9.96264 Tf +1 0 0 1 167.268 657.235 Tm [<0511>]TJ +ET +EMC +/mtext<> BDC +BT +/F15 9.96264 Tf +1 0 0 1 181.505 657.235 Tm [<00620051004B0032006700690032007400690067007200420069003F0067>]TJ +ET +EMC +/Em<> BDC +BT +/F32 9.96264 Tf +1 0 0 1 249.898 657.235 Tm [<006200690060006D002B0069006D0060>51<0032>]TJ +ET +EMC +/Artifact BMC +EMC +/Artifact BMC +BT +/F15 9.96264 Tf +1 0 0 1 303.133 89.365 Tm [<0052>]TJ +ET +EMC +/Artifact BMC +EMC +endstream +endobj +42 0 obj +<< /Type /Page /Contents 43 0 R /Resources 41 0 R /MediaBox [ 0 0 612 792 ] /StructParents 0/Tabs /S /Parent 47 0 R >> +endobj +41 0 obj +<< /ExtGState 1 0 R /Font << /F20 44 0 R /F15 45 0 R /F32 46 0 R >> >> +endobj +1 0 obj +<< /opacity1 <> >> +endobj +48 0 obj +<< /Marked true >> +endobj +49 0 obj +[ 23 0 R ] +endobj +50 0 obj +<< /Subtype /text#2Fplain/Type /EmbeddedFile /Params<> /Length 115 >> +stream +The document was declared to be of type PDF/A-4f but hasn't any attachments. LaTeX therefore added this dummy file. +endstream +endobj +51 0 obj +<< /Type /Filespec /AFRelationship /Unspecified /Desc (note about PDF/A-4F) /F (pdf-A4f.txt) /UF /EF<> >> +endobj +52 0 obj +<< /Names[(pdf-A4f) 51 0 R] >> +endobj +6 0 obj +<< /Nums [0 [ 34 0 R 35 0 R 37 0 R 31 0 R 32 0 R] +] >> +endobj +53 0 obj +<< /Limits [(ID.002) (ID.014)]/Names [(ID.002) 21 0 R (ID.003) 24 0 R (ID.004) 25 0 R (ID.005) 26 0 R (ID.006) 27 0 R (ID.007) 28 0 R (ID.008) 31 0 R (ID.009) 32 0 R (ID.010) 33 0 R (ID.011) 34 0 R (ID.012) 35 0 R (ID.013) 37 0 R (ID.014) 38 0 R ] >> +endobj +54 0 obj +<< /Kids [53 0 R] >> +endobj +7 0 obj +<< /Artifact /NonStruct /DocumentFragment /Art /Aside /Note /H7 /H6 /H8 /H6 /H9 /H6 /H10 /H6 /Title /P /FENote /Note /Sub /Span /Em /Span /Strong /Span /title /P /part /P /section /H1 /subsection /H2 /subsubsection /H3 /paragraph /H4 /subparagraph /H5 /list /L /itemize /L /enumerate /L /description /L /quote /BlockQuote /quotation /BlockQuote /verbatim /P /item /LI /itemlabel /Lbl /itembody /LBody /footnote /Note /footnotemark /Lbl /footnotelabel /Lbl /text-unit /Part /text /P /theorem-like /Sect /codeline /Span /float /Note /figures /Sect /tables /Sect >> +endobj +55 0 obj +<< /justify <> +/inline <> + >> +endobj +9 0 obj +<< /Type /Namespace /NS (http://iso.org/pdf/ssn) >> +endobj +11 0 obj +<< /Type /Namespace /NS (http://iso.org/pdf2/ssn) >> +endobj +13 0 obj +<< /Type /Namespace /NS (http://www.w3.org/1998/Math/MathML) >> +endobj +16 0 obj +<< /title [/Title 11 0 R] /part [/Title 11 0 R] /section [/H1 11 0 R] /subsection [/H2 11 0 R] /subsubsection [/H3 11 0 R] /paragraph [/H4 11 0 R] /subparagraph [/H5 11 0 R] /list [/L 11 0 R] /itemize [/L 11 0 R] /enumerate [/L 11 0 R] /description [/L 11 0 R] /quote [/BlockQuote 9 0 R] /quotation [/BlockQuote 9 0 R] /verbatim [/P 11 0 R] /item [/LI 11 0 R] /itemlabel [/Lbl 11 0 R] /itembody [/LBody 11 0 R] /footnote [/FENote 11 0 R] /footnotemark [/Lbl 11 0 R] /footnotelabel [/Lbl 11 0 R] /text-unit [/Part 11 0 R] /text [/P 11 0 R] /theorem-like [/Sect 11 0 R] /codeline [/Sub 11 0 R] /float [/Aside 11 0 R] /figures [/Sect 11 0 R] /tables [/Sect 11 0 R] >> +endobj +15 0 obj +<< /Type /Namespace /NS (https://www.latex-project.org/ns/dflt) /RoleMapNS 16 0 R >> +endobj +18 0 obj +<< /chapter [/H1 11 0 R] /section [/H2 11 0 R] /subsection [/H3 11 0 R] /subsubsection [/H4 11 0 R] /paragraph [/H5 11 0 R] /subparagraph [/H6 11 0 R] >> +endobj +17 0 obj +<< /Type /Namespace /NS (https://www.latex-project.org/ns/book) /RoleMapNS 18 0 R >> +endobj +19 0 obj +<< /Type /Namespace /NS (data:,C9B55C18-275C-494E-C10F-E4B83CD03F23) >> +endobj +8 0 obj +[ 9 0 R 11 0 R 13 0 R 15 0 R 17 0 R 19 0 R ] +endobj +21 0 obj +<< /Type /StructElem /S /Document /NS 11 0 R /P 5 0 R /K 26 0 R /ID (ID.002) >> +endobj +24 0 obj +<< /Type /StructElem /S /Artifact /NS 15 0 R /P 5 0 R /ID (ID.003) >> +endobj +25 0 obj +<< /Type /StructElem /S /Artifact /NS 15 0 R /P 5 0 R /ID (ID.004) >> +endobj +26 0 obj +<< /Type /StructElem /S /text-unit /NS 15 0 R /P 21 0 R /K 27 0 R /ID (ID.005) >> +endobj +27 0 obj +<< /Type /StructElem /C /justify /S /text /NS 15 0 R /P 26 0 R /K [ 28 0 R ] /ID (ID.006) >> +endobj +28 0 obj +<< /Type /StructElem /C /inline /AF [30 0 R] /T /S /Formula /NS 11 0 R /P 27 0 R /K [ 33 0 R] /ID (ID.007) >> +endobj +31 0 obj +<< /Type /StructElem /S /mtext /NS 13 0 R /P 33 0 R /K [<> 32 0 R ] /ID (ID.008) >> +endobj +32 0 obj +<< /Type /StructElem /S /Em /NS 11 0 R /P 31 0 R /K <> /ID (ID.009) >> +endobj +33 0 obj +<< /Type /StructElem /S /math /NS 13 0 R /P 28 0 R /K [34 0 R 35 0 R 37 0 R 38 0 R 31 0 R] /ID (ID.010) >> +endobj +34 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 33 0 R /K <> /ID (ID.011) >> +endobj +35 0 obj +<< /Type /StructElem /A 36 0 R /S /mo /NS 13 0 R /P 33 0 R /K <> /ID (ID.012) >> +endobj +37 0 obj +<< /Type /StructElem /S /mi /NS 13 0 R /P 33 0 R /K <> /ID (ID.013) >> +endobj +38 0 obj +<< /Type /StructElem /A 39 0 R /S /mspace /NS 13 0 R /P 33 0 R /ID (ID.014) >> +endobj +5 0 obj +<< /Type /StructTreeRoot /Namespaces 8 0 R /IDTree 54 0 R /ClassMap 55 0 R /ParentTree 6 0 R /RoleMap 7 0 R /K 21 0 R >> +endobj +56 0 obj +[ 43 [ 460 ] 50 [ 460 ] 96 [ 422 ] 98 [ 409 ] 105 [ 332 ] 109 [ 537 ] ] +endobj +58 0 obj +<< /Subtype /CIDFontType0C /Length 1274 >> +[BINARY STREAM] +endobj +57 0 obj +<< /Type /FontDescriptor /FontName /ADNFRA+LMRoman10-Italic /Flags 4 /FontBBox [ -458 -290 1386 1125 ] /Ascent 1125 /CapHeight 683 /Descent -290 /ItalicAngle -15 /StemV 102 /XHeight 431 /FontFile3 58 0 R >> +endobj +59 0 obj +<< /Length 757 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-ADNFRA-LMRoman10-Italic-0) +%%Title: (TeX-ADNFRA-LMRoman10-Italic-0 TeX ADNFRA-LMRoman10-Italic 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (ADNFRA-LMRoman10-Italic) +/Supplement 0 +>> def +/CMapName /TeX-Identity-ADNFRA-LMRoman10-Italic def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +6 beginbfchar +<002B> <0063> +<0032> <0065> +<0060> <0072> +<0062> <0073> +<0069> <0074> +<006D> <0075> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +46 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /ADNFRA+LMRoman10-Italic /DescendantFonts [ 60 0 R ] /ToUnicode 59 0 R >> +endobj +60 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /ADNFRA+LMRoman10-Italic /FontDescriptor 57 0 R /W 56 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +61 0 obj +[ 50 [ 444 ] 63 [ 556 ] 66 [ 278 ] 75 [ 833 ] 81 [ 500 500 ] 98 [ 394 ] 103 [ 333 ] 105 [ 389 ] 114 [ 722 ] 116 [ 528 ] ] +endobj +63 0 obj +<< /Subtype /CIDFontType0C /Length 1812 >> +[BINARY STREAM] +endobj +62 0 obj +<< /Type /FontDescriptor /FontName /GZYIXU+LMRoman10-Regular /Flags 4 /FontBBox [ -430 -290 1417 1127 ] /Ascent 1127 /CapHeight 683 /Descent -290 /ItalicAngle 0 /StemV 93 /XHeight 431 /FontFile3 63 0 R >> +endobj +64 0 obj +<< /Length 833 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-GZYIXU-LMRoman10-Regular-0) +%%Title: (TeX-GZYIXU-LMRoman10-Regular-0 TeX GZYIXU-LMRoman10-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (GZYIXU-LMRoman10-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-GZYIXU-LMRoman10-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +11 beginbfchar +<0032> <0065> +<003F> <0068> +<0042> <0069> +<004B> <006D> +<0051> <006F> +<0052> <0031> +<0062> <0073> +<0067> <0020> +<0069> <0074> +<0072> <0077> +<0074> <0078> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +45 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /GZYIXU+LMRoman10-Regular /DescendantFonts [ 65 0 R ] /ToUnicode 64 0 R >> +endobj +65 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /GZYIXU+LMRoman10-Regular /FontDescriptor 62 0 R /W 61 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +66 0 obj +[ 30 [ 778 ] 1296 [ 529 429 ] ] +endobj +68 0 obj +<< /Subtype /CIDFontType0C /Length 984 >> +[BINARY STREAM] +endobj +67 0 obj +<< /Type /FontDescriptor /FontName /NFEDYW+LatinModernMath-Regular /Flags 4 /FontBBox [ -1042 -3060 4082 3560 ] /Ascent 806 /CapHeight 683 /Descent -194 /ItalicAngle 0 /StemV 93 /XHeight 431 /FontFile3 68 0 R >> +endobj +69 0 obj +<< /Length 758 >> +stream +%!PS-Adobe-3.0 Resource-CMap +%%DocumentNeededResources: ProcSet (CIDInit) +%%IncludeResource: ProcSet (CIDInit) +%%BeginResource: CMap (TeX-NFEDYW-LatinModernMath-Regular-0) +%%Title: (TeX-NFEDYW-LatinModernMath-Regular-0 TeX NFEDYW-LatinModernMath-Regular 0) +%%Version: 1.000 +%%EndComments +/CIDInit /ProcSet findresource begin +12 dict begin +begincmap +/CIDSystemInfo +<< /Registry (TeX) +/Ordering (NFEDYW-LatinModernMath-Regular) +/Supplement 0 +>> def +/CMapName /TeX-Identity-NFEDYW-LatinModernMath-Regular def +/CMapType 2 def +1 begincodespacerange +<0000> +endcodespacerange +0 beginbfrange +endbfrange +3 beginbfchar +<001E> <003D> +<0510> +<0511> +endbfchar +endcmap +CMapName currentdict /CMap defineresource pop +end +end +%%EndResource +%%EOF +endstream +endobj +44 0 obj +<< /Type /Font /Subtype /Type0 /Encoding /Identity-H /BaseFont /NFEDYW+LatinModernMath-Regular /DescendantFonts [ 70 0 R ] /ToUnicode 69 0 R >> +endobj +70 0 obj +<< /Type /Font /Subtype /CIDFontType0 /BaseFont /NFEDYW+LatinModernMath-Regular /FontDescriptor 67 0 R /W 66 0 R /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> >> +endobj +47 0 obj +<< /Type /Pages /Count 1 /Kids [ 42 0 R ] >> +endobj +71 0 obj +<< /EmbeddedFiles 52 0 R >> +endobj +72 0 obj +<< /Type /Catalog /Pages 47 0 R /Names 71 0 R /MarkInfo 48 0 R/OutputIntents 49 0 R/Lang (en)/Metadata 40 0 R/StructTreeRoot 5 0 R >> +endobj +xref +0 73 +0000000002 65535 f +0000021940 00000 n +0000000003 00000 f +0000000004 00000 f +0000000010 00000 f +0000026513 00000 n +0000022542 00000 n +0000022917 00000 n +0000024964 00000 n +0000023599 00000 n +0000000012 00000 f +0000023667 00000 n +0000000014 00000 f +0000023737 00000 n +0000000020 00000 f +0000024500 00000 n +0000023818 00000 n +0000024773 00000 n +0000024602 00000 n +0000024875 00000 n +0000000000 00000 f +0000025025 00000 n +0000000020 00000 n +0000003353 00000 n +0000025123 00000 n +0000025211 00000 n +0000025299 00000 n +0000025399 00000 n +0000025510 00000 n +0000003586 00000 n +0000003921 00000 n +0000025661 00000 n +0000025795 00000 n +0000025916 00000 n +0000026041 00000 n +0000026162 00000 n +0000004121 00000 n +0000026294 00000 n +0000026415 00000 n +0000004192 00000 n +0000004246 00000 n +0000021853 00000 n +0000021718 00000 n +0000021005 00000 n +0000035152 00000 n +0000032636 00000 n +0000029136 00000 n +0000035518 00000 n +0000021987 00000 n +0000022023 00000 n +0000022050 00000 n +0000022301 00000 n +0000022495 00000 n +0000022613 00000 n +0000022880 00000 n +0000023496 00000 n +0000026650 00000 n +0000028096 00000 n +0000026738 00000 n +0000028319 00000 n +0000029289 00000 n +0000029488 00000 n +0000031522 00000 n +0000029626 00000 n +0000031743 00000 n +0000032790 00000 n +0000032990 00000 n +0000034106 00000 n +0000033038 00000 n +0000034334 00000 n +0000035312 00000 n +0000035580 00000 n +0000035624 00000 n +trailer +<< /Size 73 /Root 72 0 R /ID [ <2350CAD05F8A7AF0AA4058486855344F> <2350CAD05F8A7AF0AA4058486855344F> ] >> +startxref +35774 +%%EOF diff --git a/testfiles-lua/test_xml.mlr b/testfiles-lua/test_xml.mlr new file mode 100644 index 0000000..e1aa986 --- /dev/null +++ b/testfiles-lua/test_xml.mlr @@ -0,0 +1,196 @@ + + 𝑎 + = + 𝑏 + + + + ( + + + + + + 1 + + + 0 + + + 0 + + + + + 0 + + + 1 + + + 0 + + + + + 0 + + + 0 + + + 1 + + + + + + ) + + = + + { + + + + + 1 + + + if  + + 𝑎 + = + 𝑏 + + + + + + 2 + + + else + + + + + + + + + 𝑥 + = + + + + 𝑏 + ± + + + + 𝑏 + 2 + + + 4 + 𝑎 + 𝑐 + + + + + 2 + 𝑎 + + + . + + + + + 𝑎 + + + 𝑐 + _ + + + 𝑏 + . + + + 𝑐 + + + + + + + + 𝑎 + 𝑏 + 𝑐 + + + = + 𝑑 + 𝑒 + 𝑓 + + + + 𝑒 + + i + 𝜋 + + + + + = + + 1 + + + + + ( + 1 + + + 2 + + + = + 3 + ) + + + + + + + 5 + + + + + + + + + sin + ( + 𝑥 + ) + + sin + ( + 𝑥 + + + 2 + 𝜋 + ) + = + 0 + diff --git a/testfiles-lua/test_xml.mlt b/testfiles-lua/test_xml.mlt new file mode 100644 index 0000000..004279b --- /dev/null +++ b/testfiles-lua/test_xml.mlt @@ -0,0 +1,48 @@ +\DocumentMetadata{ + uncompress, + pdfversion = 2.0, + testphase = {phase-III,table,math}, +} +\input{regression-test} +\documentclass{article} +\usepackage{unicode-math} + +\begin{document} +\ExplSyntaxOn +\luamml_set_filename:n { + \jobname .mml + } +\luamml_process: +\luamml_begin_single_file: +\ExplSyntaxOff + +\[ + \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} +\] + +\[ + x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}. +\] + +\[ + \sum_a\underline c\dot bc' +\] + +\begin{align} + abc&=def & e^{\mathrm{i}\pi}&=-1\\ + \Big(1+2&=3\Big)\\ + 5 +\end{align} + +Es gilt $\sin(x)-\sin(x+2\pi)=0$. + +\end{document} diff --git a/testfiles-pdf/test.mlr b/testfiles-pdf/test.mlr new file mode 100644 index 0000000..d1bd6fb --- /dev/null +++ b/testfiles-pdf/test.mlr @@ -0,0 +1,51 @@ + + + + ( + ( + 𝑝 + + 𝑞 + ) + + ( + 𝑞 + + 𝑟 + ) + ) + + ( + 𝑝 + + ( + 𝑞 + + 𝑟 + ) + ) + + + + s + i + n + + ( + 𝑥 + ) + + + s + i + n + + ( + 𝑥 + + + 2 + 𝜋 + ) + = + 0 + diff --git a/testfiles-pdf/test.mlt b/testfiles-pdf/test.mlt new file mode 100644 index 0000000..76a0e75 --- /dev/null +++ b/testfiles-pdf/test.mlt @@ -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} + diff --git a/testfiles-pdf/test_pdf.txr b/testfiles-pdf/test_pdf.txr new file mode 100644 index 0000000..c203661 --- /dev/null +++ b/testfiles-pdf/test_pdf.txr @@ -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: + diff --git a/testfiles-pdf/test_pdf.xrt b/testfiles-pdf/test_pdf.xrt new file mode 100644 index 0000000..76a0e75 --- /dev/null +++ b/testfiles-pdf/test_pdf.xrt @@ -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} +