@@ -396,9 +396,24 @@ local function isOperatorKind (tree, typeOfAtom)
396396 return false
397397end
398398
399- local function isMoveableLimits (tree )
399+ local function isMoveableLimitsOrAlwaysStacked (tree )
400+ if not tree then
401+ return false -- safeguard
402+ end
403+ if tree .is_always_stacked then
404+ -- We use an internal flag to mark commands that are always stacking
405+ -- their sup/sub arguments, such as brace-like commands.
406+ return true
407+ end
400408 if tree .command ~= " mo" then
401- return false
409+ -- On the recursion:
410+ -- MathML allows movablelimits on <mo> elements, but "embellished operators"
411+ -- can be other elements inheriting the property from their "core operator",
412+ -- see MathML Core §3.2.4.1, which is full of intricacies so we are probably
413+ -- not even doing the right thing here.
414+ -- On the hack:
415+ -- See variant commands for limits further down.
416+ return SU .boolean (tree .is_hacked_movablelimits , false ) or isMoveableLimitsOrAlwaysStacked (tree [1 ])
402417 end
403418 if tree .options and SU .boolean (tree .options .movablelimits , false ) then
404419 return true
430445local function isAccentSymbol (symbol )
431446 return operatorDict [symbol ] and operatorDict [symbol ].atom == atoms .types .accent
432447end
448+ local function isBottomAccentSymbol (symbol )
449+ return operatorDict [symbol ] and operatorDict [symbol ].atom == atoms .types .botaccent
450+ end
433451
434452local function compileToMathML_aux (_ , arg_env , tree )
435453 if type (tree ) == " string" then
@@ -565,14 +583,15 @@ local function compileToMathML_aux (_, arg_env, tree)
565583 end
566584 tree .options = {}
567585 -- Translate TeX-like sub/superscripts to `munderover` or `msubsup`,
568- -- depending on whether the base is an operator with moveable limits.
569- elseif tree .id == " sup" and isMoveableLimits (tree [1 ]) then
586+ -- depending on whether the base is an operator with moveable limits,
587+ -- or a brace-like command.
588+ elseif tree .id == " sup" and isMoveableLimitsOrAlwaysStacked (tree [1 ]) then
570589 tree .command = " mover"
571- elseif tree .id == " sub" and isMoveableLimits (tree [1 ]) then
590+ elseif tree .id == " sub" and isMoveableLimitsOrAlwaysStacked (tree [1 ]) then
572591 tree .command = " munder"
573- elseif tree .id == " subsup" and isMoveableLimits (tree [1 ]) then
592+ elseif tree .id == " subsup" and isMoveableLimitsOrAlwaysStacked (tree [1 ]) then
574593 tree .command = " munderover"
575- elseif tree .id == " supsub" and isMoveableLimits (tree [1 ]) then
594+ elseif tree .id == " supsub" and isMoveableLimitsOrAlwaysStacked (tree [1 ]) then
576595 tree .command = " munderover"
577596 local tmp = tree [2 ]
578597 tree [2 ] = tree [3 ]
@@ -638,7 +657,7 @@ local function compileToMathML_aux (_, arg_env, tree)
638657 elseif tree .id == " command" and symbols [tree .command ] then
639658 local atom = { id = " atom" , [1 ] = symbols [tree .command ] }
640659 if isAccentSymbol (symbols [tree .command ]) and # tree > 0 then
641- -- LaTeX-style accents \vec {v} = <mover accent="true"><mi>v</mi><mo>→ </mo></mover>
660+ -- LaTeX-style accents \overrightarrow {v} = <mover accent="true"><mi>v</mi><mo>⃗ </mo></mover>
642661 local accent = {
643662 id = " command" ,
644663 command = " mover" ,
@@ -649,6 +668,18 @@ local function compileToMathML_aux (_, arg_env, tree)
649668 accent [1 ] = compileToMathML_aux (nil , arg_env , tree [1 ])
650669 accent [2 ] = compileToMathML_aux (nil , arg_env , atom )
651670 tree = accent
671+ elseif isBottomAccentSymbol (symbols [tree .command ]) and # tree > 0 then
672+ -- LaTeX-style bottom accents \underleftarrow{v} = <munder accent="true"><mi>v</mi><mo>⃮</mo></munder>
673+ local accent = {
674+ id = " command" ,
675+ command = " munder" ,
676+ options = {
677+ accentunder = " true" ,
678+ },
679+ }
680+ accent [1 ] = compileToMathML_aux (nil , arg_env , tree [1 ])
681+ accent [2 ] = compileToMathML_aux (nil , arg_env , atom )
682+ tree = accent
652683 elseif # tree > 0 then
653684 -- Play cool with LaTeX-style commands that don't take arguments:
654685 -- Edge case for non-accent symbols so we don't loose bracketed groups
@@ -728,6 +759,80 @@ registerCommand("mn", { [1] = objType.str }, function (x)
728759 return x
729760end )
730761
762+ -- Register a limit-like variant command
763+ -- Variants of superior, inferior, projective and injective limits are special:
764+ -- They accept a sub/sup behaving as a movablelimits, but also have a symbol
765+ -- on top of the limit symbol, which is not a movablelimits.
766+ -- I can't see in the MathML specification how to do this properly: MathML Core
767+ -- seems to only allow movablelimits on <mo> elements, and <mover>/<munder> may
768+ -- inherit that property from their "core operator", but in this case we do not
769+ -- want the accent to be movable, only the limit sup/sub.
770+ -- So we use a hack, and also avoid "\def" here to prevent unwanted mrows.
771+ -- @tparam string name TeX command name
772+ -- @tparam string command MathML command (mover or munder)
773+ -- @tparam number symbol Unicode codepoint for the accent symbol
774+ -- @tparam string text Text representation
775+ local function registerVarLimits (name , command , symbol , text )
776+ registerCommand (name , {}, function ()
777+ local options = command == " mover" and { accent = " true" } or { accentunder = " true" }
778+ return {
779+ command = command ,
780+ is_hacked_movablelimits = true , -- Internal flag to mark this as a hack
781+ options = options ,
782+ {
783+ command = " mo" ,
784+ options = { atom = " op" , movablelimits = false },
785+ text ,
786+ },
787+ {
788+ command = " mo" ,
789+ options = { accentunder = " true" },
790+ luautf8 .char (symbol ),
791+ },
792+ }
793+ end )
794+ end
795+ registerVarLimits (" varlimsup" , " mover" , 0x203E , " lim" ) -- U+203E OVERLINE
796+ registerVarLimits (" varliminf" , " munder" , 0x203E , " lim" ) -- U+203E OVERLINE
797+ registerVarLimits (" varprojlim" , " munder" , 0x2190 , " lim" ) -- U+2190 LEFTWARDS ARROW
798+ registerVarLimits (" varinjlim" , " munder" , 0x2192 , " lim" ) -- U+2192 RIGHTWARDS ARROW
799+
800+ -- Register a brace-like commands.
801+ -- Those symbols are accents per-se in MathML, and are non-combining in Unicode.
802+ -- But TeX treats them as "pseudo-accent" stretchy symbols.
803+ -- Moreover, they accept a sub/sup which is always stacked, and not movable.
804+ -- So we use an internal flag.
805+ -- We also avoid "\def" here to prevent unwanted mrows resulting from the
806+ -- compilation of the argument.
807+ -- @tparam string name TeX command name
808+ -- @tparam string command MathML command (mover or munder)
809+ -- @tparam number symbol Unicode codepoint for the brace symbol
810+ local function registerBraceLikeCommands (name , command , symbol )
811+ registerCommand (name , {
812+ [1 ] = objType .tree ,
813+ }, function (tree )
814+ local options = command == " mover" and { accent = " true" } or { accentunder = " true" }
815+ return {
816+ command = command ,
817+ is_always_stacked = true , -- Internal flag to mark this as a brace-like command
818+ options = options ,
819+ tree [1 ],
820+ {
821+ command = " mo" ,
822+ options = { stretchy = " true" },
823+ luautf8 .char (symbol ),
824+ },
825+ }
826+ end )
827+ end
828+ -- Note: the following overriddes the default commands from xml-entities / unicode-math.
829+ registerBraceLikeCommands (" overbrace" , " mover" , 0x23DE ) -- U+23DE TOP CURLY BRACKET
830+ registerBraceLikeCommands (" underbrace" , " munder" , 0x23DF ) -- U+23DF BOTTOM CURLY BRACKET
831+ registerBraceLikeCommands (" overparen" , " mover" , 0x23DC ) -- U+23DC TOP PARENTHESIS
832+ registerBraceLikeCommands (" underparen" , " munder" , 0x23DD ) -- U+23DD BOTTOM PARENTHESIS
833+ registerBraceLikeCommands (" overbracket" , " mover" , 0x23B4 ) -- U+23B4 TOP SQUARE BRACKET
834+ registerBraceLikeCommands (" underbracket" , " munder" , 0x23B5 ) -- U+23B5 BOTTOM SQUARE BRACKET
835+
731836compileToMathML (
732837 nil ,
733838 {},
@@ -737,7 +842,6 @@ compileToMathML(
737842 \def{sqrt}{\msqrt{#1}}
738843 \def{bi}{\mi[mathvariant=bold-italic]{#1}}
739844 \def{dsi}{\mi[mathvariant=double-struck]{#1}}
740- \def{vec}{\mover[accent=true]{#1}{\rightarrow}}
741845
742846 % From amsmath:
743847 \def{to}{\mo[atom=bin]{→}}
0 commit comments