From b12479292b3e2468c564619ee6b60dbc13eeb1be Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Tue, 19 Apr 2022 20:10:27 +0000 Subject: luamathalign (19apr22) git-svn-id: svn://tug.org/texlive/trunk@63081 c570f23f-e606-0410-a88d-b1316a301751 --- .../texmf-dist/tex/lualatex/luamathalign/build.lua | 21 ++ .../lualatex/luamathalign/luamathalign-luacmd.lua | 19 ++ .../lualatex/luamathalign/luamathalign-luaprop.lua | 42 +++ .../tex/lualatex/luamathalign/luamathalign.lua | 375 +++++++++++++++++++++ .../tex/lualatex/luamathalign/luamathalign.sty | 98 ++++++ 5 files changed, 555 insertions(+) create mode 100644 Master/texmf-dist/tex/lualatex/luamathalign/build.lua create mode 100644 Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luacmd.lua create mode 100644 Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luaprop.lua create mode 100644 Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.lua create mode 100644 Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.sty (limited to 'Master/texmf-dist/tex') diff --git a/Master/texmf-dist/tex/lualatex/luamathalign/build.lua b/Master/texmf-dist/tex/lualatex/luamathalign/build.lua new file mode 100644 index 00000000000..3ce83c670a6 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luamathalign/build.lua @@ -0,0 +1,21 @@ +module = "luamathalign" +typesetexe = "lualatex" +tdsroot = "lualatex" +checkengines = {"luatex"} +unpackfiles = {"*.dtx"} +installfiles = {"*.sty", "*.lua"} +sourcefiles = {"*.dtx", "*.lua"} + +uploadconfig = { + pkg = "luamathalign", + version = "v0.1", + author = "Marcel Krüger", + license = "lppl1.3c", + summary = "More flexible alignment in amsmath environments", + ctanPath = "/macros/luatex/latex/luamathalign/", + -- repository = "https://github.com/zauguin/luamathalign/" + description = "Allow aligning mathematical expressions on points where \z + where direcly using & is not possible, especially in nested macros or environments.", + topic = {'maths', 'luatex'}, + -- update = true, +} diff --git a/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luacmd.lua b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luacmd.lua new file mode 100644 index 00000000000..a2ff4ed0679 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luacmd.lua @@ -0,0 +1,19 @@ +----Copyright (C) 2019--2022 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. +local func_tbl = lua.get_functions_table() +local set_lua = token.set_lua +local new_luafunction = luatexbase.new_luafunction +return function(name, func, ...) + local idx = new_luafunction(name) + set_lua(name, idx, ...) + func_tbl[idx] = func +end diff --git a/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luaprop.lua b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luaprop.lua new file mode 100644 index 00000000000..3e10f0fc454 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign-luaprop.lua @@ -0,0 +1,42 @@ +----Copyright (C) 2019--2022 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. +return function(namespace) + return { + query = function(n) + local p = node.getproperty(n) + return p and p[namespace] + end, + get = function(n, ...) + local p = node.getproperty(n) + if not p then + p = {} + node.setproperty(n, p) + end + local n = p[namespace] + if nil == p[namespace] then + local d = select('#', ...) ~= 0 and ... or {} + n, p[namespace] = d, d + end + return n + end, + set = function(n, v) + local p = node.getproperty(n) + if not p then + p = {} + node.setproperty(n, p) + end + local old = p[namespace] + p[namespace] = v + return old + end, + } +end diff --git a/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.lua b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.lua new file mode 100644 index 00000000000..aa8476b0553 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.lua @@ -0,0 +1,375 @@ +-- +-- This is file `luamathalign.lua', +-- generated with the docstrip utility. +-- +-- The original source files were: +-- +-- luamathalign.dtx (with options: `lua') +-- +-- Copyright (C) 2019--2022 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. +local luacmd = require'luamathalign-luacmd' +local luaprop = require'luamathalign-luaprop'('mathalign') +local hlist = node.id'hlist' +local vlist = node.id'vlist' +local whatsit = node.id'whatsit' +local kern = node.id'kern' +local user_defined = node.subtype'user_defined' +local whatsit_id = luatexbase.new_whatsit'mathalign' +local node_cmd = token.command_id'node' +local ampersand = token.new(38, 4) +-- We might want to add y later +local function is_marked(mark, list) + for n in node.traverse(list) do + local id = n.id + if id == hlist or id == vlist then + if is_marked(mark, n.head) then return true end + elseif id == whatsit and n.subtype == user_defined + and n.user_id == whatsit_id and n.value == mark then + return true + end + end + return false +end +local function assert_unmarked(mark, list, ...) + local marked = is_marked(mark, list) + if marked then + tex.error("Multiple alignment marks", "I found multiple alignment marks \z + of type " .. mark .. " in an alignment where I already had an \z + alignment mark of that type. You should look at both of them and \z + decide which one is right. I will continue with the first one for now.") + end + return ... +end +local measure do + local vmeasure + local function hmeasure(mark, list) + local x, last = 0, list.head + for n in node.traverse(last) do + local id = n.id + if id == hlist then + local w, h, d = node.rangedimensions(list, last, n) + x, last = x + w, n + local dx = hmeasure(mark, n) + if dx then return assert_unmarked(mark, n.next, dx + x) end + elseif id == vlist then + local w, h, d = node.rangedimensions(list, last, n) + x, last = x + w, n + local dx = vmeasure(mark, n) + if dx then return assert_unmarked(mark, n.next, dx + x) end + elseif id == whatsit and n.subtype == user_defined + and n.user_id == whatsit_id and n.value == mark then + local w, h, d = node.rangedimensions(list, last, n) + local after + list.head, after = node.remove(list.head, n) + return assert_unmarked(mark, after, x + w) + end + end + end + function vmeasure(mark, list) + for n in node.traverse(list.head) do + local id = n.id + if id == hlist then + local dx = hmeasure(mark, n) + if dx then return assert_unmarked(mark, n.next, dx + n.shift) end + elseif id == vlist then + local dx = vmeasure(mark, n) + if dx then return assert_unmarked(mark, n.next, dx + n.shift) end + elseif id == whatsit and n.subtype == user_defined + and n.user_id == whatsit_id and n.value == mark then + local after + list.head, after = node.remove(list.head, n) + return assert_unmarked(mark, after, 0) + end + end + end + function measure(mark, head) + local x, last = 0, head + for n in node.traverse(last) do + local id = n.id + if id == hlist then + local w, h, d = node.dimensions(last, n) + x, last = x + w, n + local dx = hmeasure(mark, n) + if dx then return assert_unmarked(mark, n.next, head, dx + x) end + elseif id == vlist then + local w, h, d = node.dimensions(last, n) + x, last = x + w, n + local dx = vmeasure(mark, n) + if dx then return assert_unmarked(mark, n.next, head, dx + x) end + elseif id == whatsit and n.subtype == user_defined + and n.user_id == whatsit_id and n.value == mark then + local w, h, d = node.dimensions(last, n) + local after + head, after = node.remove(head, n) + return assert_unmarked(mark, after, head, x + w) + end + end + return head + end +end + +local isolate do + local visolate + local function hisolate(list, offset) + local x, last = 0, list.head + local newhead, newtail = nil, nil + local n = last + while n do + local id = n.id + if id == hlist then + local w, h, d = node.rangedimensions(list, last, n) + x, last = x + w, n + local inner_head, inner_tail, new_offset = hisolate(n, offset - x) + if inner_head then + if newhead then + newtail.next, inner_head.prev = inner_head, newtail + else + newhead = inner_head + end + newtail = inner_tail + offset = x + new_offset + end + n = n.next + elseif id == vlist then + local w, h, d = node.rangedimensions(list, last, n) + x, last = x + w, n + local inner_head, inner_tail, new_offset = visolate(n, offset - x) + if inner_head then + if newhead then + newtail.next, inner_head.prev = inner_head, newtail + else + newhead = inner_head + end + newtail = inner_tail + offset = x + new_offset + end + n = n.next + elseif id == whatsit and n.subtype == user_defined + and n.user_id == whatsit_id then + local w, h, d = node.rangedimensions(list, last, n) + x = x + w + list.head, last = node.remove(list.head, n) + if x ~= offset then + local k = node.new(kern) + k.kern, offset = x - offset, x + newhead, newtail = node.insert_after(newhead, newtail, k) + end + newhead, newtail = node.insert_after(newhead, newtail, n) + n = last + else + n = n.next + end + end + return newhead, newtail, offset + end + function visolate(list, offset) + local newhead, newtail = nil, nil + local n = list.head + while n do + local id = n.id + if id == hlist then + if dx then return assert_unmarked(mark, n.next, dx + n.shift) end + local inner_head, inner_tail, new_offset = hisolate(n, offset) + if inner_head then + if newhead then + newtail.next, inner_head.prev = inner_head, newtail + else + newhead = inner_head + end + newtail = inner_tail + offset = new_offset + end + n = n.next + elseif id == vlist then + if dx then return assert_unmarked(mark, n.next, dx + n.shift) end + local inner_head, inner_tail, new_offset = visolate(n, offset) + if inner_head then + if newhead then + newtail.next, inner_head.prev = inner_head, newtail + else + newhead = inner_head + end + newtail = inner_tail + offset = new_offset + end + n = n.next + elseif id == whatsit and n.subtype == user_defined + and n.user_id == whatsit_id then + local after + list.head, after = node.remove(list.head, n) + if 0 ~= offset then + local k = node.new(kern) + k.kern, offset = -offset, 0 + newhead, newtail = node.insert_after(newhead, newtail, k) + end + newhead, newtail = node.insert_after(newhead, newtail, n) + n = last + else + n = n.next + end + end + return newhead, newtail, offset + end + function isolate(head) + local x, last = 0, head + local newhead, newtail, offset = nil, nil, 0 + local n = last + while n do + local id = n.id + if id == hlist then + local w, h, d = node.dimensions(last, n) + x, last = x + w, n + local inner_head, inner_tail, new_offset = hisolate(n, offset - x) + if inner_head then + if newhead then + newtail.next, inner_head.prev = inner_head, newtail + else + newhead = inner_head + end + newtail = inner_tail + offset = x + new_offset + end + n = n.next + elseif id == vlist then + local w, h, d = node.dimensions(last, n) + x, last = x + w, n + local inner_head, inner_tail, new_offset = visolate(n, offset - x) + if inner_head then + if newhead then + newtail.next, inner_head.prev = inner_head, newtail + else + newhead = inner_head + end + newtail = inner_tail + offset = x + new_offset + end + n = n.next + elseif id == whatsit and n.subtype == user_defined + and n.user_id == whatsit_id then + local w, h, d = node.dimensions(last, n) + x = x + w + head, last = node.remove(head, n) + if x ~= offset then + local k = node.new(kern) + k.kern, offset = x - offset, x + newhead, newtail = node.insert_after(newhead, newtail, k) + end + newhead, newtail = node.insert_after(newhead, newtail, n) + n = last + else + n = n.next + end + end + return head, newhead + end +end + +local mark, afterkern +luatexbase.add_to_callback('post_mlist_to_hlist_filter', function(n) + if mark then + local x + n, x = measure(mark, n) + local k = node.new'kern' + local off = x - n.width + k.kern, afterkern.kern = off, -off + node.insert_after(n.head, nil, k) + n.width = x + mark, afterkern = nil, nil + end + return n +end, 'luamathalign') + +local function get_kerntoken(newmark) + assert(not mark) + mark, afterkern = newmark, node.new'kern' + return token.new(node.direct.todirect(afterkern), node_cmd) +end + +local function insert_whatsit(mark) + local n = node.new(whatsit, user_defined) + n.user_id, n.type, n.value = whatsit_id, string.byte'd', mark + node.write(n) +end +luacmd("SetAlignmentPoint", function() + local mark = token.scan_int() + if mark < 0 then + for i=tex.nest.ptr,0,-1 do + local t = tex.nest[i].head + if luaprop.query(t) ~= nil then + mark = mark + 1 + if mark == 0 then + luaprop.set(t, true) + return insert_whatsit(-i) + end + end + end + tex.error('No compatible alignment environment found', + 'This either means that \\SetAlignmentPoint was used outside\n\z + of an alignment or the used alignment is not setup for use with\n\z + luamathalign. In the latter case you might want to look at\n\z + non-negative alignment marks.') + else + return insert_whatsit(mark) + end +end, "protected") + +function handle_whatsit(mark) + token.put_next(ampersand, get_kerntoken(mark)) +end +luacmd("ExecuteAlignment", function() + return handle_whatsit(token.scan_int()) +end, "protected") + +luacmd("LuaMathAlign@begin", function() + local nest = tex.nest.top + luaprop.set(nest.head, false) +end, "protected") +luacmd("LuaMathAlign@end@early", function() + local t = tex.nest.top.head + if luaprop.set(t, nil) == true then + handle_whatsit(-tex.nest.ptr) + end +end, "protected") +local delayed +luacmd("LuaMathAlign@end", function() + local nest = tex.nest.top + local t = nest.head + if luaprop.set(t, nil) == true then + assert(not delayed) + delayed = {get_kerntoken(-tex.nest.ptr), ampersand} + end +end, "protected") +luatexbase.add_to_callback("hpack_filter", function(head, groupcode) + if delayed and groupcode == "align_set" then +-- HACK: token.put_next puts the tokens into the input stream after the cell +-- is fully read, before the next starts. This will act as if the content was +-- written as the first element of the next field. + token.put_next(delayed) + delayed = nil + end + return true +end, "luamathalign.delayed") + +luacmd("LuaMathAlign@IsolateAlignmentPoints", function() + local main = token.scan_int() + if not token.scan_keyword 'into' then + tex.error'Expected "into"' + end + local marks = token.scan_int() + local head, newhead = isolate(tex.box[main]) + tex.box[marks] = node.direct.tonode(node.direct.hpack( + newhead and node.direct.todirect(newhead) or 0)) +end, "protected") +-- +-- +-- End of file `luamathalign.lua'. diff --git a/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.sty b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.sty new file mode 100644 index 00000000000..6a735b40120 --- /dev/null +++ b/Master/texmf-dist/tex/lualatex/luamathalign/luamathalign.sty @@ -0,0 +1,98 @@ +%% +%% This is file `luamathalign.sty', +%% generated with the docstrip utility. +%% +%% The original source files were: +%% +%% luamathalign.dtx (with options: `package') +%% +%% Copyright (C) 2019--2022 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. +\NeedsTeXFormat{LaTeX2e} +\ProvidesPackage + {luamathalign} + [2022-04-18 v0.1 additional math alignment tricks using Lua] +\directlua{require'luamathalign'} +\IfPackageLoadedTF{amsmath}{% + \@firstofone +}{% + \AddToHook{package/amsmath/after} +} +{% + \def\align@preamble{% + &\hfil + \strut@ + \setboxz@h{\@lign$\m@th\displaystyle{% + \LuaMathAlign@begin##\LuaMathAlign@end}$}% + \ifmeasuring@\savefieldlength@\fi + \set@field + \tabskip\z@skip + &\setboxz@h{\@lign$\m@th\displaystyle{{}##}$}% + \ifmeasuring@\savefieldlength@\fi + \set@field + \hfil + \tabskip\alignsep@ + } + \renewcommand{\start@aligned}[2]{% + \RIfM@\else + \nonmatherr@{\begin{\@currenvir}}% + \fi + \savecolumn@ % Assumption: called inside a group + \alignedspace@left + \if #1t\vtop \else \if#1b \vbox \else \vcenter \fi \fi \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{\LuaMathAlign@begin##\LuaMathAlign@end}$% + \tabskip\z@skip + &\column@plus + $\m@th\displaystyle{{}##}$% + \hfil + \tabskip\alignsep@ + \crcr + } + \edef\math@cr@@@alignedat{\LuaMathAlign@end@early + \unexpanded\expandafter{\math@cr@@@alignedat}} + \edef\math@cr{\LuaMathAlign@end@early + \unexpanded\expandafter{\math@cr}} + \edef\endaligned{\LuaMathAlign@end@early + \unexpanded\expandafter{\endaligned}} +} +\protected\def\AlignHere{\SetAlignmentPoint\m@ne} +\begingroup + \def\patch@finph@nt\setbox\tw@\null{% + \LuaMathAlign@IsolateAlignmentPoints\z@ into \tw@ + }% +\expanded{\endgroup% +\protected\def\noexpand\finph@nt{% + \unexpanded\expandafter\expandafter\expandafter{% + \expandafter\patch@finph@nt\finph@nt + }% +}} +\ExplSyntaxOff +\endinput +%% +%% End of file `luamathalign.sty'. -- cgit v1.2.3