From 2bd77f5a4d02b0d1649589aa7a548e79b198f6af Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Thu, 10 Jun 2021 03:01:49 +0000 Subject: CTAN sync 202106100301 --- macros/luatex/generic/minim-math/minim-math.lua | 482 ++++++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 macros/luatex/generic/minim-math/minim-math.lua (limited to 'macros/luatex/generic/minim-math/minim-math.lua') diff --git a/macros/luatex/generic/minim-math/minim-math.lua b/macros/luatex/generic/minim-math/minim-math.lua new file mode 100644 index 0000000000..194e938819 --- /dev/null +++ b/macros/luatex/generic/minim-math/minim-math.lua @@ -0,0 +1,482 @@ + +local M = {} + +local alloc = require ('minim-alloc') +local callbacks = require ('minim-callbacks') + +alloc.remember('minim-math') + +-- 1 Alphabet tables + +--[[ There are gaps left in the unicode math alphabets +-- for characters that were already represented +-- elsewhere. These tables will be used for redirecting +-- characters mapped to these gaps. +--]] + +local gaps = + { [0x1d455] = 0x0210e -- H + , [0x1d49d] = 0x0212c -- script B + , [0x1d4a0] = 0x02130 -- script E + , [0x1d4a1] = 0x02131 -- script F + , [0x1d4a3] = 0x0210b -- script H + , [0x1d4a4] = 0x02110 -- script I + , [0x1d4a7] = 0x02112 -- script L + , [0x1d4a8] = 0x02133 -- script M + , [0x1d4ad] = 0x0211b -- script R + , [0x1d4ba] = 0x0212f -- script e + , [0x1d4bc] = 0x0210a -- script g + , [0x1d4c4] = 0x02134 -- script o + , [0x1d506] = 0x0212d -- fraktur C + , [0x1d50b] = 0x0210c -- fraktur H + , [0x1d50c] = 0x02111 -- fraktur I + , [0x1d515] = 0x0211c -- fraktur R + , [0x1d51d] = 0x02128 -- fraktur Z + , [0x1d53a] = 0x02102 -- bb C + , [0x1d53f] = 0x0210d -- bb H + , [0x1d545] = 0x02115 -- bb N + , [0x1d547] = 0x02119 -- bb P + , [0x1d548] = 0x0211a -- bb Q + , [0x1d549] = 0x0211d -- bb R + , [0x1d551] = 0x02124 -- bb Z + } + +-- greek variants have irregular upright values +local varcorr = + { [0x03f4] = 0x03a2 -- ϴ + , [0x2207] = 0x03aa -- ∇ + , [0x2202] = 0x03ca -- ∂ + , [0x03f5] = 0x03cb -- ϵ + , [0x03d1] = 0x03cc -- ϑ + , [0x03f0] = 0x03cd -- ϰ + , [0x03d5] = 0x03ce -- ϕ + , [0x03f1] = 0x03cf -- ϱ + , [0x03d6] = 0x03d0 -- ϖ + } + +--[[ When transforming to other stylistic variants, we need +-- to know which alphabet a character is in; the transformation +-- offsets differ for each alphabet. +--]] + +local function getalphabet (n) + if n >= 0x61 and n <= 0x7A then return 0x61 -- latin + elseif n >= 0x41 and n <= 0x5A then return 0x41 -- Latin + elseif n >= 0x30 and n <= 0x39 then return 0x30 -- digits + elseif n >= 0x3b1 and n <= 0x3c9 then return 0x3b1 -- greek + elseif n >= 0x391 and n <= 0x3a9 then return 0x391 -- Greek + elseif (varcorr[n] or 0xfff) < 0x3c0 then return 0x391 -- varGreek + elseif (varcorr[n] or 0xfff) < 0x3f0 then return 0x3b1 -- vargreek + else return 0 -- the character cannot transform + end +end + +--[[ In the offsets table, all transformations can be found for the first +-- character in the alphabet. The numbers in the table are increments. +--]] + +local offsets = + { [0] = {} + , [0x61] = -- latin + { [1] = 0x1d3ed -- it 𝑎 = 0x1d44e + , [3] = 0x1d421 -- bfit 𝒂 = 0x1d482 + , [4] = 0x1d4f1 -- bb 𝕒 = 0x1d552 + , [2] = 0x1d3b9 -- bf 𝐚 = 0x1d41a + , [5] = 0x1d4bd -- frak 𝔞 = 0x1d51e + , [6] = 0x1d525 -- bffrak 𝖆 = 0x1d586 + , [7] = 0x1d455 -- cal 𝒶 = 0x1d4b6 + , [8] = 0x1d489 -- bfcal 𝓪 = 0x1d4ea + , [9] = 0x1d559 -- sans 𝖺 = 0x1d5ba + , [10]= 0x1d5c1 -- sfit 𝘢 = 0x1d622 + , [11]= 0x1d58d -- sfbf 𝗮 = 0x1d5ee + , [12]= 0x1d5f5 -- sfbfit 𝙖 = 0x1d656 + , [13]= 0x1d629 -- mono 𝚊 = 0x1d68a + } + , [0x41] = -- Latin + { [1] = 0x1d3f3 -- it 𝐴 = 0x1d434 + , [3] = 0x1d427 -- bfit 𝑨 = 0x1d468 + , [4] = 0x1d4f7 -- bb 𝔸 = 0x1d538 + , [2] = 0x1d3bf -- bf 𝐀 = 0x1d400 + , [5] = 0x1d4c3 -- frak 𝔄 = 0x1d504 + , [6] = 0x1d52b -- bffrak 𝕬 = 0x1d56c + , [7] = 0x1d45b -- cal 𝒜 = 0x1d49c + , [8] = 0x1d48f -- bfcal 𝓐 = 0x1d4d0 + , [9] = 0x1d55f -- sans 𝖠 = 0x1d5a0 + , [10]= 0x1d5c7 -- sfit 𝘈 = 0x1d608 + , [11]= 0x1d593 -- sfbf 𝗔 = 0x1d5d4 + , [12]= 0x1d5fb -- sfbfit 𝘼 = 0x1d63c + , [13]= 0x1d62f -- mono 𝙰 = 0x1d670 + } + , [0x30] = -- digits + { [2] = 0x1d79e -- bf 𝐀 = 0x1d7ce + , [4] = 0x1d7a8 -- bb 𝔸 = 0x1d7d8 + , [9] = 0x1d7b2 -- sans 𝟬 = 0x1d47e2 + , [11]= 0x1d7bc -- bfsans 𝟬 = 0x1d47ec + , [13]= 0x1d7c6 -- mono 𝟬 = 0x1d47f7 + } + , [0x3b1] = -- greek + { [1] = 0x1d34b -- it 𝛼 = 0x1d6fc + , [2] = 0x1d311 -- bf 𝛂 = 0x1d6c2 + , [3] = 0x1d385 -- bfit 𝜶 = 0x1d736 + , [11]= 0x1d3bf -- sfbf 𝝖 = 0x1d756 + , [12]= 0x1d3f9 -- sfbfit 𝞪 = 0x1d7aa + } + , [0x391] = -- Greek + { [1] = 0x1d351 -- it 𝛢 = 0x1d6e2 + , [2] = 0x1d317 -- bf 𝚨 = 0x1d6a8 + , [3] = 0x1d38b -- bfit 𝜜 = 0x1d71c + , [11]= 0x1d3c5 -- sfbf 𝝖 = 0x1d756 + , [12]= 0x1d3ff -- sfbfit 𝞐 = 0x1d790 + } + } + +--[[ The function transform_char() returns a transformed character, or nil if +-- the character cannot be transformed like that. It is used by the setmap +-- function (which gives a warning on nil) and by the mlist callback (which +-- ignores nil). +--]] + +local function add_fam(num) + local _, fam, _ = tex.getmathcodes(num) + return num, fam +end + +local function transform_char(num, style) + if style == 0 then + return add_fam(num) + else + local base = getalphabet(num) + local n = offsets[base][style] + if n == nil then + return nil, nil + else + local res = varcorr[num] or num + res = gaps[n+res] or n+res + return add_fam(res) + end + end +end + +-- 1 Parsing user input + +--[[ We accept the following as argument to our lua-side functions: +-- * An alphabet or class name; +-- * A numerical range of the form [0-9]+@[0-9]+ +-- * Ordinary strings +--]] + +-- math alphabets like 𝒶𝒷𝒸, 𝔞𝔟𝔠, 𝕒𝕓𝕔, and classes like ord, rel etc. +local alphabets = alloc.saved_table('math:alphabets') + +local function add_to_alphabet(num, code) + alphabets[code] = (alphabets[code] or '') .. string.utfcharacter(num) +end + +local function chars_iterator(input) + local a = alphabets[input] + if a then + return string.utfvalues(a) + else + local from, to = string.match(input, '^([0-9]+)@([0-9]+)$') + if from then + local i, n = tonumber(from) - 1, tonumber(to) + return function() + i = i + 1 + if i <= n then return gaps[i] or i end + end + else + return string.utfvalues(input) + end + end +end + +local function apply_settings(input, fn, ...) + for char in chars_iterator(input) do + fn(char, ...) + end +end + +-- 1 Switching class and family + +--[[ Setting class and family information is rather simple: the difficult part +-- of this package is switching between math alphabets. +--]] + +local classes = {} +classes.ord = 0 +classes.op = 1 +classes.bin = 2 +classes.rel = 3 +classes.fence = 3 +classes.open = 4 +classes.close = 5 +classes.punct = 6 +classes.var = 7 + +local function setclass(char, class) + local _, fam , sym = tex.getmathcodes (char) + tex.setmathcode (char, classes[class] or class, fam, sym) +end + +local function setfam(char, fam) + local class, _, sym = tex.getmathcodes (char) + tex.setmathcode (char, class, fam, sym) +end + +function M.setclass (input, class) + apply_settings(input, setclass, class) +end + +function M.setfam (input, fam) + apply_settings(input, setfam, fam) +end + +-- 1 Character variants + +--[[ The settings of variant characters is not coupled to the style mechanism: +-- rather, it changes the \Umathcode-s directly. Of course, the variant +-- characters can transform. +--]] + +M.greek_variants = + { [0x3b5] = 0x3f5 -- ε ϵ + , [0x398] = 0x3f4 -- Θ ϴ + , [0x3b8] = 0x3d1 -- θ ϑ + , [0x3ba] = 0x3f0 -- κ ϰ + , [0x3c6] = 0x3d5 -- φ ϕ + , [0x3c1] = 0x3f1 -- ρ ϱ + , [0x3c0] = 0x3d6 -- π ϖ + } + +function M.usevariant (char) + for num in string.utfvalues (char) do + local class, fam, _ = tex.getmathcodes(num) + tex.setmathcode (num, class, fam, M.greek_variants[num] or num) + end +end + +function M.usedefault (char) + for num in string.utfvalues (char) do + local class, fam, _ = tex.getmathcodes(num) + tex.setmathcode (num, class, fam, num) + end +end + +-- 1 Styles + +--[[ The style table gives a numerical value to every math style. The value +-- `clear' unsets the style attribute. +--]] + +local styles = {} +styles.clear = alloc.unset +styles.up = 0 +styles.rm = 0 +styles.it = 1 +styles.mit = 1 +styles.bf = 2 +styles.bfit = 3 +styles.bb = 4 +styles.frak = 5 +styles.bffrak = 6 +styles.cal = 7 +styles.scr = 7 +styles.script = 7 +styles.bfcal = 8 +styles.bfscr = 8 +styles.sf = 9 +styles.sans = 9 +styles.sfit = 10 +styles.sfbf = 11 +styles.sfbfit = 12 +styles.mono = 13 +styles.tt = 13 + +local style_attribute = alloc.new_attribute ('math style attribute') + +function M.setstyle (style) + tex.setattribute (style_attribute, styles[style]) +end + +-- 1 Switching alphabets + +--[[ Which glyph a variable character should transform to is determined by +-- a tex \count register. This ensures that changes to the math style respect +-- grouping. +-- +-- On the lua side, we keep track of these counts in a table (it is filled +-- from the tex side). +--]] + +local transformcodes = alloc.saved_table ('math:transformcodes') + +-- set a character as transformable +local function set_transform (num, char) + c = alloc.new_count ('math transform '..char) + transformcodes[num] = c + tex.setcount ('global', c, num) +end + +-- query the destination +local function dest_char (num) + local c = transformcodes[num] + if c == nil then + return num, nil + else + return add_fam(tex.count[c]) + end +end + +-- set the destination +local function setmap (char, style) + local target = transform_char (char, styles[style]) + if target == nil then + texio.write_nl ('! minim warning: style '..style..' not available for char '..char..'.') + else + tex.count[transformcodes[char]] = target + end +end + +function M.setmap(input, style) + apply_settings(input, setmap, style) +end + +--[[ To allow saving and restoring math style settings, the following function +-- is a dump of \count and \Umathcode values. +--]] + +function M.save_mathstyles () + for _,v in pairs(transformcodes) do + tex.sprint('\\count'..v..'='..tex.count[v]..'\\relax') + end + for v,_ in pairs(M.greek_variants) do + local a,b,c = tex.getmathcodes(v) + tex.sprint('\\Umathcode'..v..'='..a..' '..b..' '..c..'\\relax') + end +end + +-- 1 The transformation callback + +--[[ The stilistic mappings are done in the mlist_to_mlist callback. The +-- function noad_iterator returns an iterator over the noad list; all +-- math_char nodes are inspected. +-- +-- The listmathfields table contains all noad fields that can contain another +-- list of noads. +--]] + +local listmathfields = { 'head', 'nucleus', 'sub', 'sup', 'accent', 'bot_accent', + 'display', 'text', 'script', 'scriptscript', 'num', 'denom', 'degree', 'next' } + +local function noad_iterator (head) + local nodelist = { link=nil, content=head } + return function () + if nodelist == nil then return nil end + local n = nodelist.content + nodelist = nodelist.link + for _,f in pairs(listmathfields) do + if node.has_field (n, f) and n[f] ~= nil then + nodelist = { link=nodelist, content=n[f] } + end + end + return n + end +end + +local math_char = node.id ('math_char') + +local function inspect_noads (h,d,n) + for nd in noad_iterator (h) do + if nd.id == math_char then + local sa = node.has_attribute(nd, style_attribute) + local char, fam + if sa then + char, fam = transform_char(nd.char, sa) + else + char, fam = dest_char(nd.char) + end + nd.char, nd.fam = char or nd.char, fam or nd.fam + end + end + -- if we would not use minim-callbacks, we would have to call + -- node.mlist_to_hlist (h,d,n) here + return true +end + +callbacks.register ('mlist_to_mlist', inspect_noads) + +-- 1 Reading the math character table + +--[[ The following are used for processing the character table. Control +-- sequences are defined from the lua side; for accents and radicals this +-- means using tex-side helper macros. +-- +-- When using the add_mathchar function with accents or radicals, please keep +-- in mind that it assumes \catcode`\:=11. +--]] + +local default_fam = 0 + +local accents = + { accent = 'accent{}' + , botaccent = 'accent{bottom}' + , overlay = 'accent{overlay}' + , over = 'stack{}' + , under = 'stack{bottom}' + } +local is_delimiter = + { fence = true + , open = true + , close = true + } + +local function tex_accent(class, num, char) + kind = kind or '' + return '\\math:'..accents[class]..'{'..num..'}'..char +end + +local function add_mathchar(code, char, class, cs, alphabet) + if char:sub(0,1) == ' ' then + -- accents can be given above a space (compare ' ́' with '́') + char = char:sub(2) + end + local class_nr = classes[class] + if class_nr then + tex.setmathcode(code, class_nr, default_fam, code) + if cs then token.set_macro(cs, char) end + if class_nr == 7 then set_transform(code, char) end + if is_delimiter[class] then tex.setdelcode(code, default_fam, code, 0, 0) end + elseif class == 'radical' then + if cs then + token.set_macro(cs, '\\math:radical{'..code..'}'..char) + tex.print('\\mathlet\\'..char..'\\'..cs) + end + else + -- TODO: in the future, allow accent characters by re-ordering them + tex.setmathcode(code, 0, default_fam, 0) -- provisional + if cs then token.set_macro(cs, tex_accent(class, code, char)) end + end + add_to_alphabet(code, class) + if alphabet then + add_to_alphabet(code, alphabet) + end +end + +function M.add_mathchar(t) + add_mathchar(t.code, t.char, t.class, t.cs, t.alphabet) +end + +-- 1 Tex-side user interface + +alloc.luadef('math:mathcls', function() M.setclass(token.scan_string(), token.scan_string()) end) +alloc.luadef('math:mathfam', function() M.setfam(token.scan_string(), token.scan_string()) end) +alloc.luadef('math:mathmap', function() M.setmap(token.scan_string(), token.scan_string()) end) + +alloc.luadef('usemathvariant', function() M.usevariant(token.scan_string()) end, 'protected') +alloc.luadef('usemathdefault', function() M.usedefault(token.scan_string()) end, 'protected') +alloc.luadef('mathstyle', function() M.setstyle(token.scan_string()) end, 'protected') + +-- + +return M + -- cgit v1.2.3