Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
74.24% |
441 / 594 |
|
51.16% |
22 / 43 |
CRAP | |
0.00% |
0 / 1 |
| BaseParsing | |
74.24% |
441 / 594 |
|
51.16% |
22 / 43 |
1233.54 | |
0.00% |
0 / 1 |
| accent | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
8.02 | |||
| array | |
80.00% |
12 / 15 |
|
0.00% |
0 / 1 |
13.15 | |||
| alignAt | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
10 | |||
| boldsymbol | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| cancel | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| menclose | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
| cancelTo | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| chemCustom | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
| customLetters | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| cFrac | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
| crLaTeX | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| dots | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
| genFrac | |
78.05% |
32 / 41 |
|
0.00% |
0 / 1 |
14.79 | |||
| frac | |
50.00% |
8 / 16 |
|
0.00% |
0 / 1 |
8.12 | |||
| hline | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| hskip | |
70.00% |
7 / 10 |
|
0.00% |
0 / 1 |
6.97 | |||
| handleOperatorName | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
| matrix | |
100.00% |
59 / 59 |
|
100.00% |
1 / 1 |
25 | |||
| over | |
96.97% |
32 / 33 |
|
0.00% |
0 / 1 |
10 | |||
| oint | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
8.02 | |||
| overset | |
55.56% |
10 / 18 |
|
0.00% |
0 / 1 |
2.35 | |||
| phantom | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
3 | |||
| raiseLower | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
72 | |||
| underset | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| underOver | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
| mathFont | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| mathChoice | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
132 | |||
| makeBig | |
85.19% |
23 / 27 |
|
0.00% |
0 / 1 |
15.73 | |||
| machine | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setFont | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| sideset | |
90.00% |
36 / 40 |
|
0.00% |
0 / 1 |
12.14 | |||
| spacer | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| smash | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
72 | |||
| texAtom | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
5 | |||
| intent | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
240 | |||
| hBox | |
67.65% |
23 / 34 |
|
0.00% |
0 / 1 |
22.62 | |||
| setStyle | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| not | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| vbox | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
| sqrt | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
3 | |||
| tilde | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| xArrow | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
2 | |||
| getApplyFct | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
4 | |||
| 1 | <?php |
| 2 | namespace MediaWiki\Extension\Math\WikiTexVC\MMLmappings; |
| 3 | |
| 4 | use IntlChar; |
| 5 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Tag; |
| 6 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\TexClass; |
| 7 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\TexConstants\Variants; |
| 8 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLParsingUtil; |
| 9 | use MediaWiki\Extension\Math\WikiTexVC\MMLmappings\Util\MMLutil; |
| 10 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLarray; |
| 11 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLbase; |
| 12 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmerror; |
| 13 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmfrac; |
| 14 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmi; |
| 15 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmmultiscripts; |
| 16 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmo; |
| 17 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmover; |
| 18 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmpadded; |
| 19 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmphantom; |
| 20 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmroot; |
| 21 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmrow; |
| 22 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmspace; |
| 23 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsqrt; |
| 24 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmstyle; |
| 25 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmsup; |
| 26 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtable; |
| 27 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtd; |
| 28 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtext; |
| 29 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmtr; |
| 30 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunder; |
| 31 | use MediaWiki\Extension\Math\WikiTexVC\MMLnodes\MMLmunderover; |
| 32 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\DQ; |
| 33 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\FQ; |
| 34 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun1; |
| 35 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun2; |
| 36 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun2sq; |
| 37 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\Fun4; |
| 38 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\Literal; |
| 39 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\Matrix; |
| 40 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexArray; |
| 41 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\TexNode; |
| 42 | use MediaWiki\Extension\Math\WikiTexVC\Nodes\UQ; |
| 43 | use MediaWiki\Extension\Math\WikiTexVC\TexUtil; |
| 44 | |
| 45 | /** |
| 46 | * Parsing functions for specific recognized mappings. |
| 47 | * Usually the parsing functions are invoked from the BaseMethods classes. |
| 48 | */ |
| 49 | class BaseParsing { |
| 50 | |
| 51 | public static function accent( $node, $passedArgs, $name, $operatorContent, |
| 52 | string $accent, ?bool $stretchy = null ): MMLbase { |
| 53 | // Currently this is own implementation from Fun1.php |
| 54 | // TODO The first if-clause is mathjax specific (and not necessary by generic parsers) |
| 55 | // and will most probably removed (just for running all tc atm) |
| 56 | if ( $accent == "00B4" || $accent == "0060" ) { |
| 57 | $attrs = [ Tag::SCRIPTTAG => "true" ]; |
| 58 | } else { |
| 59 | if ( $stretchy === null ) { |
| 60 | $attrs = []; |
| 61 | } else { |
| 62 | $attrs = [ "stretchy" => $stretchy ? "true" : "false" ]; |
| 63 | } |
| 64 | } |
| 65 | if ( trim( $operatorContent ) === '\\vec' ) { |
| 66 | $attrs['class'] = 'mwe-math-vec'; |
| 67 | } |
| 68 | |
| 69 | // Fetching entity from $accent key tbd |
| 70 | $entity = MMLutil::createEntity( $accent ); |
| 71 | if ( !$entity ) { |
| 72 | $entity = $accent; |
| 73 | } |
| 74 | $inner = $node->getArg()->toMMLtree( $passedArgs ); |
| 75 | |
| 76 | return MMLmover::newSubtree( |
| 77 | !$inner->isEmpty() ? $inner : ( new MMLmrow() ), |
| 78 | new MMLmo( '', $attrs, $entity ) |
| 79 | ); |
| 80 | } |
| 81 | |
| 82 | public static function array( $node, $passedArgs, $operatorContent, $name, $begin = null, $open = null, |
| 83 | $close = null, $align = null, $spacing = null, |
| 84 | $vspacing = null, $style = null, $raggedHeight = null |
| 85 | ): MMLbase { |
| 86 | $output = []; |
| 87 | if ( $open != null ) { |
| 88 | $resDelimiter = TexUtil::getInstance()->delimiter( trim( $open ) ) ?? false; |
| 89 | if ( $resDelimiter ) { |
| 90 | // $retDelim = $bm->checkAndParseDelimiter($open, $node,$passedArgs,true); |
| 91 | $output[] = ( new MMLmo( TexClass::OPEN, [], $resDelimiter[0] ) ); |
| 92 | } |
| 93 | } |
| 94 | if ( $name == "Bmatrix" || $name == "bmatrix" || $name == "Vmatrix" |
| 95 | || $name == "vmatrix" || $name == "smallmatrix" || $name == "pmatrix" || $name == "matrix" ) { |
| 96 | // This is a workaround and might be improved mapping BMatrix to Matrix directly instead of array |
| 97 | return self::matrix( $node, $passedArgs, $operatorContent, $name, |
| 98 | $open, $close, null, null, null, null, true ); |
| 99 | |
| 100 | } else { |
| 101 | $output[] = new MMLmrow( TexClass::ORD, [], $node->getMainarg()->toMMLtree() ); |
| 102 | } |
| 103 | |
| 104 | if ( $close != null ) { |
| 105 | $resDelimiter = TexUtil::getInstance()->delimiter( trim( $close ) ) ?? false; |
| 106 | if ( $resDelimiter ) { |
| 107 | $output[] = new MMLmo( TexClass::CLOSE, [], $resDelimiter[0] ); |
| 108 | } |
| 109 | } |
| 110 | return new MMLarray( ...$output ); |
| 111 | } |
| 112 | |
| 113 | public static function alignAt( Matrix $node, $passedArgs, $operatorContent, $name, $align = null, |
| 114 | $smth2 = null |
| 115 | ): MMLbase { |
| 116 | $mtable = new MMLmtable( '', [ 'displaystyle' => 'true' ] ); |
| 117 | $inner = []; |
| 118 | $align ??= $node->getAlign(); |
| 119 | $rowNo = 0; |
| 120 | $rowCount = count( $node->getArgs() ); |
| 121 | foreach ( $node as $tableRow ) { |
| 122 | $rowNo++; |
| 123 | $mtds = []; |
| 124 | $colNo = 0; |
| 125 | $isEmptyLine = true; |
| 126 | $attributes = []; |
| 127 | $rowSpecs = $tableRow->getRowSpecs(); |
| 128 | if ( $rowSpecs ) { |
| 129 | $attributes['style'] = "padding-bottom: {$rowSpecs->getCssLength()};"; |
| 130 | } |
| 131 | foreach ( $tableRow->getArgs() as $tableCell ) { |
| 132 | $class = ''; |
| 133 | if ( in_array( $align[$colNo] ?? [], [ 'l', 'r' ] ) ) { |
| 134 | // @phan-suppress-next-line PhanTypeArraySuspiciousNullable |
| 135 | $class .= ' mwe-math-columnalign-' . $align[$colNo]; |
| 136 | } |
| 137 | $class = trim( $class ); |
| 138 | $isEmptyLine = $isEmptyLine && $tableCell->isEmpty(); |
| 139 | $mtds[] = new MMLmtd( "", |
| 140 | $attributes + ( $class ? [ 'class' => $class ] : [] ), |
| 141 | $tableCell->toMMLtree() ); |
| 142 | $colNo++; |
| 143 | } |
| 144 | // empty trailing lines with only one empty cell should not be rendered |
| 145 | if ( $rowNo === $rowCount && $colNo === 1 && $isEmptyLine ) { |
| 146 | continue; |
| 147 | } |
| 148 | $inner[] = new MMLmtr( "", [], ...$mtds ); |
| 149 | } |
| 150 | $mtable->addChild( ...$inner ); |
| 151 | return $mtable; |
| 152 | } |
| 153 | |
| 154 | public static function boldsymbol( $node, $passedArgs, $operatorContent, $name, $smth = null, |
| 155 | $smth2 = null |
| 156 | ): MMLbase { |
| 157 | $passedArgs = array_merge( $passedArgs, [ 'mathvariant' => Variants::BOLDITALIC ] ); |
| 158 | return new MMLmrow( TexClass::ORD, [], $node->getArg()->toMMLtree( $passedArgs ) ); |
| 159 | } |
| 160 | |
| 161 | public static function cancel( Fun1 $node, $passedArgs, $operatorContent, $name, $notation = '' ): MMLbase { |
| 162 | return self::menclose( $node->getArg()->toMMLtree(), $notation ); |
| 163 | } |
| 164 | |
| 165 | private static function menclose( MMLbase $content, string $notation ): MMLbase { |
| 166 | $classes = [ 'menclose' ]; |
| 167 | foreach ( explode( ' ', $notation ) as $element ) { |
| 168 | $classes[] = 'menclose-' . $element; |
| 169 | } |
| 170 | |
| 171 | return new MMLmrow( '', [ 'class' => implode( ' ', $classes ) ], |
| 172 | new MMLmrow( '', [], $content ) ); |
| 173 | } |
| 174 | |
| 175 | public static function cancelTo( $node, $passedArgs, $operatorContent, $name, string $notation = '' ): MMLbase { |
| 176 | // Match MathJax's 0.1em upward adjustment for the cancelto annotation: |
| 177 | // https://github.com/mathjax/MathJax-src/blob/master/ts/input/tex/cancel/CancelConfiguration.ts |
| 178 | $mpAdded = new MMLmpadded( "", [ "depth" => "-.1em", "voffset" => ".1em", |
| 179 | "class" => "mwe-math-cancelto" ], |
| 180 | $node->getArg1()->toMMLtree() ); |
| 181 | $menclose = self::menclose( $node->getArg2()->toMMLtree(), $notation ); |
| 182 | return new MMLmrow( TexClass::ORD, [], MMLmsup::newSubtree( $menclose, $mpAdded ) ); |
| 183 | } |
| 184 | |
| 185 | public static function chemCustom( $node, $passedArgs, $operatorContent, $name, $translation = null ): MMLmerror { |
| 186 | return MMLmerror::newFromText( $translation ?: 'tbd chemCustom' ); |
| 187 | } |
| 188 | |
| 189 | public static function customLetters( $node, $passedArgs, $operatorContent, $name, $char, |
| 190 | $isOperator = false |
| 191 | ): MMLbase { |
| 192 | if ( $isOperator ) { |
| 193 | return new MMLmrow( TexClass::ORD, [], new MMLmo( "", [], $char ) ); |
| 194 | } |
| 195 | $variant = $passedArgs['mathvariant'] ?? Variants::NORMAL; |
| 196 | return new MMLmrow( TexClass::ORD, [], new MMLmi( "", [ 'mathvariant' => $variant ], $char ) ); |
| 197 | } |
| 198 | |
| 199 | public static function cFrac( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 200 | $mstyle1 = new MMLmstyle( "", [ "displaystyle" => "false", "scriptlevel" => "0" ], |
| 201 | new MMLmrow( TexClass::ORD, [], $node->getArg1()->toMMLtree() ) ); |
| 202 | $mstyle2 = new MMLmstyle( "", [ "displaystyle" => "false", "scriptlevel" => "0" ], |
| 203 | new MMLmrow( TexClass::ORD, [], $node->getArg2()->toMMLtree() ) ); |
| 204 | // See TexUtilMMLTest testcase 81 |
| 205 | // (mml3 might be erronous here, but this element seems to be rendered correctly) |
| 206 | $whatIsThis = new MMLmrow( TexClass::ORD, [], |
| 207 | new MMLmpadded( "", [ "depth" => "3pt", "height" => "8.6pt", "width" => "0" ] ) ); |
| 208 | $inner = new MMLmrow( TexClass::ORD, [], $whatIsThis, $mstyle2 ); |
| 209 | $mfrac = MMLmfrac::newSubtree( |
| 210 | new MMLmrow( TexClass::ORD, [], $whatIsThis, $mstyle1 ), $inner ); |
| 211 | return new MMLmrow( TexClass::ORD, [], $mfrac ); |
| 212 | } |
| 213 | |
| 214 | public static function crLaTeX( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 215 | return new MMLmspace( "", [ "linebreak" => "newline" ] ); |
| 216 | } |
| 217 | |
| 218 | public static function dots( $node, $passedArgs, $operatorContent, $name, $smth = null, $smth2 = null ): MMLbase { |
| 219 | switch ( $operatorContent['dots'] ?? 'dotso' ) { |
| 220 | case 'dotsb': |
| 221 | return new MMLmo( "", $passedArgs, "⋯" ); |
| 222 | case 'dotsi': |
| 223 | return new MMLarray( ( new Literal( '\\!' ) )->toMMLTree(), |
| 224 | new MMLmo( "", $passedArgs, "⋯" ) ); |
| 225 | case 'rightdelim': |
| 226 | return new MMLarray( new MMLmo( "", $passedArgs, "…" ), |
| 227 | ( new Literal( '\\,' ) )->toMMLTree() ); |
| 228 | default: |
| 229 | return new MMLmo( "", $passedArgs, "…" ); |
| 230 | } |
| 231 | } |
| 232 | |
| 233 | public static function genFrac( $node, $passedArgs, $operatorContent, $name, |
| 234 | $left = null, $right = null, $thick = null, $style = null |
| 235 | ): MMLbase { |
| 236 | // Actually this is in AMSMethods, consider refactoring left, right, thick, style |
| 237 | $bm = new BaseMethods(); |
| 238 | $ret = $bm->checkAndParseDelimiter( $name, $node, $passedArgs, $operatorContent, true ); |
| 239 | if ( !$ret->isEmpty() ) { |
| 240 | // TBD |
| 241 | if ( $left == null ) { |
| 242 | $left = $ret; |
| 243 | } |
| 244 | if ( $right == null ) { |
| 245 | $right = $ret; |
| 246 | } |
| 247 | if ( $thick == null ) { |
| 248 | $thick = $ret; |
| 249 | } |
| 250 | if ( $style == null ) { |
| 251 | $style = trim( $ret ); |
| 252 | } |
| 253 | } |
| 254 | $attrs = []; |
| 255 | $displayStyle = "false"; |
| 256 | if ( in_array( $thick, [ 'thin', 'medium', 'thick', '0' ], true ) ) { |
| 257 | $attrs = array_merge( $attrs, [ "linethickness" => $thick ] ); |
| 258 | } |
| 259 | if ( $style !== '' ) { |
| 260 | $styleDigit = intval( $style, 10 ); |
| 261 | $styleAlpha = [ 'D', 'T', 'S', 'SS' ][$styleDigit]; |
| 262 | if ( $styleAlpha == null ) { |
| 263 | return new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], "Bad math style" ) ); |
| 264 | } |
| 265 | |
| 266 | if ( $styleAlpha === 'D' ) { |
| 267 | // MathJax fences \genfrac with \bigg in display style and \big otherwise |
| 268 | // swh:1:cnt:191254e9b9aedba501b4f734fb8f0b6cd9217050;lines=377-378 |
| 269 | $displayStyle = "true"; |
| 270 | $styleAttr = [ "minsize" => TexUtil::getInstance()->callback( '\\bigg' )[2] ]; |
| 271 | |
| 272 | } else { |
| 273 | $styleAttr = [ "minsize" => TexUtil::getInstance()->callback( '\\big' )[2] ]; |
| 274 | } |
| 275 | } else { |
| 276 | // Inherit delimiter size and script level when no explicit style was requested. |
| 277 | $styleAttr = []; |
| 278 | } |
| 279 | $output = []; |
| 280 | if ( $left ) { |
| 281 | $mrowOpen = new MMLmrow( TexClass::OPEN, [], new MMLmo( "", $styleAttr, $left ) ); |
| 282 | $output[] = $mrowOpen; |
| 283 | } |
| 284 | // TeX sets the parts in text style unless \genfrac asks for display style. |
| 285 | $state1 = [ 'styleargs' => [ 'displaystyle' => $displayStyle ] ]; |
| 286 | $state2 = $state1; |
| 287 | $mrow1 = new MMLmrow( TexClass::ORD, [], $node->getArg1()->toMMLtree( [], $state1 ) ); |
| 288 | $mrow2 = new MMLmrow( TexClass::ORD, [], $node->getArg2()->toMMLtree( [], $state2 ) ); |
| 289 | |
| 290 | $output[] = MMLmfrac::newSubtree( $mrow1, $mrow2, "", $attrs ); |
| 291 | if ( $right ) { |
| 292 | $mrowClose = new MMLmrow( TexClass::CLOSE, [], new MMLmo( "", $styleAttr, $right ) ); |
| 293 | $output[] = $mrowClose; |
| 294 | } |
| 295 | $output = new MMLmrow( TexClass::ORD, [], ...$output ); |
| 296 | if ( $style !== '' ) { |
| 297 | $output = new MMLmstyle( "", [ "displaystyle" => $displayStyle, "scriptlevel" => "0" ], $output ); |
| 298 | } |
| 299 | |
| 300 | return new MMLmrow( TexClass::ORD, [], $output ); |
| 301 | } |
| 302 | |
| 303 | public static function frac( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 304 | // TeX sets the parts of a fraction in text style. |
| 305 | $state = [ 'styleargs' => [ 'displaystyle' => 'false' ] ]; |
| 306 | $state1 = $state; |
| 307 | $state2 = $state; |
| 308 | if ( $node instanceof Fun2 ) { |
| 309 | $inner = [ new MMLmrow( TexClass::ORD, [], $node->getArg1()->toMMLtree( [], $state1 ) ), |
| 310 | new MMLmrow( TexClass::ORD, [], $node->getArg2()->toMMLtree( [], $state2 ) ) ]; |
| 311 | } elseif ( $node instanceof DQ ) { |
| 312 | $inner = [ new MMLmrow( TexClass::ORD, [], $node->getBase()->toMMLtree( [], $state1 ) ), |
| 313 | new MMLmrow( TexClass::ORD, [], $node->getDown()->toMMLtree( [], $state2 ) ) ]; |
| 314 | } else { |
| 315 | $inner = []; |
| 316 | foreach ( $node->getArgs() as $arg ) { |
| 317 | $argState = $state; |
| 318 | $rendered = is_string( $arg ) ? $arg : $arg->toMMLtree( [], $argState ); |
| 319 | $inner[] = new MMLmrow( TexClass::ORD, [], $rendered ); |
| 320 | } |
| 321 | } |
| 322 | $mfrac = MMLmfrac::newSubtree( $inner[0], $inner[1] ); |
| 323 | return new MMLmrow( TexClass::ORD, [], $mfrac ); |
| 324 | } |
| 325 | |
| 326 | public static function hline( $node, $passedArgs, $operatorContent, $name, |
| 327 | $smth1 = null, $smth2 = null, $smth3 = null, $smth4 = null |
| 328 | ): MMLbase { |
| 329 | // HLine is most probably not parsed this way, since only parsed in Matrix context |
| 330 | return new MMLmrow( "tbd", [], new MMLmtext( "", [], "HLINE TBD" ) ); |
| 331 | } |
| 332 | |
| 333 | public static function hskip( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 334 | if ( $node->getArg()->isCurly() ) { |
| 335 | $unit = MMLutil::squashLitsToUnit( $node->getArg() ); |
| 336 | if ( !$unit ) { |
| 337 | return new MMLarray(); |
| 338 | } |
| 339 | $em = MMLutil::dimen2em( $unit ); |
| 340 | } else { |
| 341 | // Prevent parsing in unmapped cases |
| 342 | return new MMLarray(); |
| 343 | } |
| 344 | // Added kern j4t |
| 345 | if ( $name == "mskip" || $name == "mkern" || "kern" ) { |
| 346 | $args = [ "width" => $em ]; |
| 347 | } else { |
| 348 | return new MMLarray(); |
| 349 | } |
| 350 | |
| 351 | return new MMLmspace( "", $args ); |
| 352 | } |
| 353 | |
| 354 | public static function handleOperatorName( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 355 | // In example "\\operatorname{a}" |
| 356 | $applyFct = self::getApplyFct( $operatorContent ); |
| 357 | $mmlNot = new MMLarray(); |
| 358 | if ( isset( $operatorContent['not'] ) && $operatorContent['not'] ) { |
| 359 | $mmlNot = MMLParsingUtil::createNot(); |
| 360 | } |
| 361 | $passedArgs = array_merge( $passedArgs, [ Tag::CLASSTAG => TexClass::OP, 'mathvariant' => Variants::NORMAL ] ); |
| 362 | $state = [ 'squashLiterals' => true ]; |
| 363 | $inner = $node->getArg()->toMMLtree( $passedArgs, $state ); |
| 364 | if ( $inner instanceof MMLarray && count( $inner->getChildren() ) == 1 ) { |
| 365 | $mi = $inner->getChildren()[0]; |
| 366 | // this check needs to be made explicit for phan |
| 367 | if ( $mi instanceof MMLmi ) { |
| 368 | $inner = $mi; |
| 369 | } |
| 370 | } |
| 371 | return new MMLarray( $mmlNot, $inner, $applyFct ); |
| 372 | } |
| 373 | |
| 374 | public static function matrix( Matrix $node, $passedArgs, $operatorContent, |
| 375 | $name, $open = null, $close = null, $align = null, $spacing = null, |
| 376 | $vspacing = null, $style = null, $cases = null, $numbered = null |
| 377 | ): MMLbase { |
| 378 | $resInner = []; |
| 379 | $boarder = $node->getBoarder(); |
| 380 | if ( !$align ) { |
| 381 | $align = $node->getAlign(); |
| 382 | } |
| 383 | $rowNo = 0; |
| 384 | $lines = $node->getLines(); |
| 385 | foreach ( $node as $row ) { |
| 386 | $innerInnter = []; |
| 387 | $colNo = 0; |
| 388 | $isEmptyLine = true; |
| 389 | $rowAttributes = []; |
| 390 | $rowSpecs = $row->getRowSpecs(); |
| 391 | if ( $rowSpecs ) { |
| 392 | $rowAttributes['style'] = "padding-bottom: {$rowSpecs->getCssLength()};"; |
| 393 | } |
| 394 | foreach ( $row as $cell ) { |
| 395 | $usedArg = clone $cell; |
| 396 | if ( $usedArg instanceof TexArray && |
| 397 | $usedArg->getLength() >= 1 |
| 398 | ) { |
| 399 | $firstArg = $usedArg[0]; |
| 400 | if ( $firstArg instanceof Literal && |
| 401 | $firstArg->getArg() === '\\hline ' |
| 402 | ) { |
| 403 | $usedArg->pop(); |
| 404 | } |
| 405 | } |
| 406 | $mtdAttributes = $rowAttributes; |
| 407 | $texclass = $lines[$rowNo] ? TexClass::TOP : ''; |
| 408 | $texclass .= $lines[$rowNo + 1] ?? false ? ' ' . TexClass::BOTTOM : ''; |
| 409 | $texclass .= $boarder[$colNo] ?? false ? ' ' . TexClass::LEFT : ''; |
| 410 | $texclass .= $boarder[$colNo + 1 ] ?? false ? ' ' . TexClass::RIGHT : ''; |
| 411 | if ( in_array( $align[$colNo] ?? [], [ 'l', 'r' ] ) ) { |
| 412 | // @phan-suppress-next-line PhanTypeArraySuspiciousNullable |
| 413 | $texclass .= ' mwe-math-columnalign-' . $align[$colNo]; |
| 414 | } |
| 415 | $texclass = trim( $texclass ); |
| 416 | if ( $texclass ) { |
| 417 | $mtdAttributes['class'] = $texclass; |
| 418 | } |
| 419 | // TeX sets each cell as a $...$ formula, which starts in text style. |
| 420 | // plain.tex \matrix: swh:1:cnt:0f363a96c0e1f93d830e97534b1fe07861fdae69;lines=1093-1096 |
| 421 | // amsmath matrix: swh:1:cnt:d4a287b9531788cd614d524bdd53f38681a63902;lines=2525-2527 |
| 422 | $state = [ 'inMatrix' => true, 'styleargs' => [ 'displaystyle' => 'false' ] ]; |
| 423 | $isEmptyLine = $isEmptyLine && $usedArg->isEmpty(); |
| 424 | $innerInnter[] = new MMLmtd( "", $mtdAttributes, $usedArg->toMMLtree( $passedArgs, $state ) ); |
| 425 | $colNo++; |
| 426 | } |
| 427 | $rowNo++; |
| 428 | // empty trailing lines with only one empty cell should not be rendered |
| 429 | if ( $rowNo === count( $lines ) && $colNo === 1 && $isEmptyLine ) { |
| 430 | break; |
| 431 | } |
| 432 | $resInner[] = new MMLmtr( "", [], ...$innerInnter ); |
| 433 | } |
| 434 | $mtable = new MMLmtable( '', |
| 435 | $name === 'smallmatrix' ? |
| 436 | [ 'class' => 'mwe-math-smallmatrix' ] : [] |
| 437 | ); |
| 438 | if ( $cases || ( $open != null && $close != null ) ) { |
| 439 | $bm = new BaseMethods(); |
| 440 | $mmlMoOpen = $bm->checkAndParseDelimiter( $open, $node, [], [], |
| 441 | true, TexClass::OPEN ); |
| 442 | if ( $mmlMoOpen->isEmpty() ) { |
| 443 | $mmlMoOpen = new MMLmo( TexClass::OPEN, [], $open ?? '' ); |
| 444 | } |
| 445 | |
| 446 | // MathJax gives an empty closing fence the width of a null delimiter. |
| 447 | $closeAtts = [ "data-mwe-fence" => "true", "stretchy" => "true", "symmetric" => "true" ]; |
| 448 | $mmlMoClose = $bm->checkAndParseDelimiter( $close, $node, $closeAtts, |
| 449 | null, true, TexClass::CLOSE ); |
| 450 | if ( $mmlMoClose->isEmpty() ) { |
| 451 | $mmlMoClose = ( new MMLmo( TexClass::CLOSE, $closeAtts, $close ?? '' ) ); |
| 452 | } |
| 453 | $mtable->addChild( ...$resInner ); |
| 454 | return new MMLmrow( TexClass::ORD, [], $mmlMoOpen, $mtable, $mmlMoClose ); |
| 455 | } |
| 456 | $mtable->addChild( ...$resInner ); |
| 457 | return $mtable; |
| 458 | } |
| 459 | |
| 460 | public static function over( $node, $passedArgs, $operatorContent, $name, $id = null ): MMLbase { |
| 461 | $attributes = []; |
| 462 | $start = new MMLarray(); |
| 463 | $tail = new MMLarray(); |
| 464 | if ( trim( $name ) === "\\atop" ) { |
| 465 | $attributes = [ "linethickness" => "0" ]; |
| 466 | } elseif ( trim( $name ) == "\\choose" ) { |
| 467 | // Let the operator dictionary stretch the parentheses to the contextual fraction height. |
| 468 | $start = new MMLmrow( TexClass::OPEN, [], |
| 469 | new MMLmo( "", [], "(" ) |
| 470 | ); |
| 471 | $tail = new MMLmrow( TexClass::CLOSE, [], |
| 472 | new MMLmo( "", [], ")" ) |
| 473 | ); |
| 474 | $attributes = [ "linethickness" => "0" ]; |
| 475 | } |
| 476 | // TeX sets the parts of a fraction in text style. |
| 477 | $state = [ 'styleargs' => [ 'displaystyle' => 'false' ] ]; |
| 478 | $state1 = $state; |
| 479 | $state2 = $state; |
| 480 | if ( $node instanceof Fun2 ) { |
| 481 | $mfrac = MMLmfrac::newSubtree( new MMLmrow( "", [], $node->getArg1()->toMMLtree( [], $state1 ) ), |
| 482 | new MMLmrow( "", [], $node->getArg2()->toMMLtree( [], $state2 ) ), "", $attributes ); |
| 483 | if ( $start->isEmpty() ) { |
| 484 | return $mfrac; |
| 485 | } |
| 486 | return new MMLmrow( TexClass::ORD, [], $start, $mfrac, $tail ); |
| 487 | } |
| 488 | $inner = []; |
| 489 | foreach ( $node->getArgs() as $arg ) { |
| 490 | if ( is_string( $arg ) && str_contains( $arg, $name ) ) { |
| 491 | continue; |
| 492 | } |
| 493 | $argState = $state; |
| 494 | $rendered = $arg instanceof TexNode ? $arg->toMMLtree( [], $argState ) : $arg; |
| 495 | $inner[] = new MMLmrow( "", [], $rendered ); |
| 496 | } |
| 497 | $mfrac = MMLmfrac::newSubtree( $inner[0], $inner[1], "", $attributes ); |
| 498 | if ( $start->isEmpty() ) { |
| 499 | return $mfrac; |
| 500 | } |
| 501 | return new MMLmrow( TexClass::ORD, [], $start, $mfrac, $tail ); |
| 502 | } |
| 503 | |
| 504 | public static function oint( $node, $passedArgs, $operatorContent, |
| 505 | $name, $uc = null, $attributes = null, $smth2 = null |
| 506 | ): MMLbase { |
| 507 | // This is a custom mapping not in js. |
| 508 | switch ( trim( $name ) ) { |
| 509 | case "\\oint": |
| 510 | case "\\P": |
| 511 | return new MMLmo( "", [], MMLutil::uc2xNotation( $uc ) ); |
| 512 | case "\\oiint": |
| 513 | case "\\oiiint": |
| 514 | case "\\ointctrclockwise": |
| 515 | case "\\varointclockwise": |
| 516 | // FIXME: the trailing thin space has no known source |
| 517 | return new MMLmrow( TexClass::ORD, [], |
| 518 | new MMLmstyle( "", [ "mathsize" => "2.07em" ], |
| 519 | new MMLmtext( "", $attributes, MMLutil::uc2xNotation( $uc ) ), |
| 520 | new MMLmspace( "", [ "width" => MMLutil::round2em( 3 / 18 ) ] ) |
| 521 | ) |
| 522 | ); |
| 523 | default: |
| 524 | return MMLmerror::newFromText( "not found in OintMethod" ); |
| 525 | } |
| 526 | } |
| 527 | |
| 528 | public static function overset( $node, $passedArgs, $operatorContent, $name, $id = null ): MMLbase { |
| 529 | if ( $node instanceof DQ ) { |
| 530 | return new MMLmrow( TexClass::ORD, [], |
| 531 | MMLmover::newSubtree( new MMLmrow( |
| 532 | "", |
| 533 | [], |
| 534 | $node->getDown()->toMMLtree() ), |
| 535 | $node->getDown()->toMMLtree() |
| 536 | ) |
| 537 | ); |
| 538 | } |
| 539 | return new MMLmrow( TexClass::ORD, [], |
| 540 | MMLmover::newSubtree( |
| 541 | new MMLmrow( |
| 542 | "", |
| 543 | [], |
| 544 | $node->getArg2()->toMMLtree() ), |
| 545 | $node->getArg1()->toMMLtree() |
| 546 | ) |
| 547 | ); |
| 548 | } |
| 549 | |
| 550 | public static function phantom( $node, $passedArgs, $operatorContent, |
| 551 | $name, $vertical = null, $horizontal = null, $smh3 = null |
| 552 | ): MMLbase { |
| 553 | $attrs = []; |
| 554 | if ( $vertical ) { |
| 555 | $attrs = array_merge( $attrs, [ "width" => "0" ] ); |
| 556 | } |
| 557 | if ( $horizontal ) { |
| 558 | $attrs = array_merge( $attrs, [ "depth" => "0", "height" => "0" ] ); |
| 559 | } |
| 560 | return new MMLmrow( |
| 561 | TexClass::ORD, |
| 562 | [], |
| 563 | new MMLmrow( |
| 564 | TexClass::ORD, |
| 565 | [], |
| 566 | new MMLmpadded( |
| 567 | "", |
| 568 | $attrs, |
| 569 | new MMLmphantom( "", [], $node->getArg()->toMMLtree() ) |
| 570 | ) |
| 571 | ) |
| 572 | ); |
| 573 | } |
| 574 | |
| 575 | public static function raiseLower( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 576 | if ( !$node instanceof Fun2 ) { |
| 577 | return new MMLarray(); |
| 578 | } |
| 579 | |
| 580 | $arg1 = $node->getArg1(); |
| 581 | // the second check is to avoid a false positive for PhanTypeMismatchArgumentSuperType |
| 582 | if ( $arg1->isCurly() && $arg1 instanceof TexArray ) { |
| 583 | $unit = MMLutil::squashLitsToUnit( $arg1 ); |
| 584 | if ( !$unit ) { |
| 585 | return new MMLarray(); |
| 586 | } |
| 587 | $em = MMLutil::dimen2em( $unit ); |
| 588 | if ( !$em ) { |
| 589 | return new MMLarray(); |
| 590 | } |
| 591 | } else { |
| 592 | return new MMLarray(); |
| 593 | } |
| 594 | |
| 595 | if ( trim( $name ) === "\\raise" ) { |
| 596 | $args = [ "height" => MMLutil::addPreOperator( $em, "+" ), |
| 597 | "depth" => MMLutil::addPreOperator( $em, "-" ), |
| 598 | "voffset" => MMLutil::addPreOperator( $em, "+" ) ]; |
| 599 | } elseif ( trim( $name ) === "\\lower" ) { |
| 600 | $args = [ "height" => MMLutil::addPreOperator( $em, "-" ), |
| 601 | "depth" => MMLutil::addPreOperator( $em, "+" ), |
| 602 | "voffset" => MMLutil::addPreOperator( $em, "-" ) ]; |
| 603 | } else { |
| 604 | // incorrect name, should not happen, prevent erroneous mappings from getting rendered. |
| 605 | return new MMLarray(); |
| 606 | } |
| 607 | return new MMLmrow( "", [], new MMLmpadded( "", $args, $node->getArg2()->toMMLtree() ) ); |
| 608 | } |
| 609 | |
| 610 | public static function underset( $node, $passedArgs, $operatorContent, $name, $smh = null ): MMLbase { |
| 611 | $inrow = $node->getArg2()->toMMLtree(); |
| 612 | $arg1 = $node->getArg1()->toMMLtree(); |
| 613 | if ( !$inrow->isEmpty() && !$arg1->isEmpty() ) { |
| 614 | return new MMLmrow( TexClass::ORD, [], MMLmunder::newSubtree( $inrow, $arg1 ) ); |
| 615 | } |
| 616 | |
| 617 | // If there are no two elements in munder, not render munder |
| 618 | return new MMLmrow( TexClass::ORD, [], $inrow, $arg1 ); |
| 619 | } |
| 620 | |
| 621 | public static function underOver( Fun1 $node, $passedArgs, $operatorContent, |
| 622 | $name, $operatorSymbol = null, $stack = null ): MMLbase { |
| 623 | // tbd verify if stack interpreted correctly ? |
| 624 | $texClass = $stack ? TexClass::OP : TexClass::ORD; // ORD or "" |
| 625 | |
| 626 | $fname = $node->getFname(); |
| 627 | if ( str_starts_with( $fname, '\\over' ) ) { |
| 628 | $movun = new MMLmover(); |
| 629 | } elseif ( str_starts_with( $fname, '\\under' ) ) { |
| 630 | $movun = new MMLmunder(); |
| 631 | } else { |
| 632 | // incorrect name, should not happen, prevent erroneous mappings from getting rendered. |
| 633 | return MMLmerror::newFromText( |
| 634 | 'underOver rendering requires macro to start with either \\under or \\over.' ); |
| 635 | } |
| 636 | |
| 637 | $mo = new MMLmo( "", [], $operatorSymbol ); |
| 638 | return new MMLmrow( $texClass, [], $movun::newSubtree( $node->getArg()->toMMLtree( $passedArgs ), $mo ) ); |
| 639 | } |
| 640 | |
| 641 | public static function mathFont( $node, $passedArgs, $operatorContent, $name, $mathvariant = null ): MMLbase { |
| 642 | $args = MMLParsingUtil::getFontArgs( $name, $mathvariant, $passedArgs ); |
| 643 | $state = []; |
| 644 | |
| 645 | return new MMLmrow( TexClass::ORD, [], $node->getArg()->toMMLtree( $args, $state ) ); |
| 646 | } |
| 647 | |
| 648 | public static function mathChoice( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase { |
| 649 | if ( !$node instanceof Fun4 ) { |
| 650 | return MMLmerror::newFromText( "Wrong node type in mathChoice" ); |
| 651 | } |
| 652 | |
| 653 | /** |
| 654 | * Parametrization for mathchoice: |
| 655 | * \mathchoice |
| 656 | * {<material for display style>} |
| 657 | * {<material for text style>} |
| 658 | * {<material for script style>} |
| 659 | * {<material for scriptscript style>} |
| 660 | */ |
| 661 | |
| 662 | if ( isset( $operatorContent["styleargs"] ) ) { |
| 663 | $styleArgs = $operatorContent["styleargs"]; |
| 664 | $displayStyle = $styleArgs["displaystyle"] ?? "true"; |
| 665 | $scriptLevel = $styleArgs["scriptlevel"] ?? "0"; |
| 666 | |
| 667 | if ( $displayStyle == "true" && $scriptLevel == "0" ) { |
| 668 | // This is displaystyle |
| 669 | return $node->getArg1()->toMMLtree( $passedArgs, $operatorContent ); |
| 670 | } elseif ( $displayStyle == "false" && $scriptLevel == "0" ) { |
| 671 | // This is textstyle |
| 672 | return $node->getArg2()->toMMLtree( $passedArgs, $operatorContent ); |
| 673 | } elseif ( $displayStyle == "false" && $scriptLevel == "1" ) { |
| 674 | // This is scriptstyle |
| 675 | return $node->getArg3()->toMMLtree( $passedArgs, $operatorContent ); |
| 676 | } elseif ( $displayStyle == "false" && $scriptLevel == "2" ) { |
| 677 | // This is scriptscriptstyle |
| 678 | return $node->getArg4()->toMMLtree( $passedArgs, $operatorContent ); |
| 679 | } |
| 680 | } |
| 681 | // By default render displaystyle |
| 682 | return $node->getArg1()->toMMLtree( $passedArgs, $operatorContent ); |
| 683 | } |
| 684 | |
| 685 | public static function makeBig( $node, $passedArgs, $operatorContent, $name, $texClass = null, |
| 686 | $size = null |
| 687 | ): MMLbase { |
| 688 | // $size is the kernel height of \big..\Bigg (8.5pt..17.5pt) scaled by MathJax's 1.2/.85 |
| 689 | // swh:1:cnt:72f94735bba07ecbc4214f3cc409de0e137ff1ee;lines=1534-1541 |
| 690 | // swh:1:cnt:4fcf2badb30fa89a86be332c1481b0daaf21d367;lines=47 |
| 691 | $passedArgs = array_merge( $passedArgs, [ "maxsize" => $size, "minsize" => $size ] ); |
| 692 | // Sieve arg if it is a delimiter (it seems args are not applied here |
| 693 | $bm = new BaseMethods(); |
| 694 | $argcurrent = trim( $node->getArg() ); |
| 695 | switch ( $argcurrent ) { |
| 696 | case "\\|": |
| 697 | case "\\vert": |
| 698 | case "|": |
| 699 | case "\\uparrow": |
| 700 | case "\\downarrow": |
| 701 | case "\\Uparrow": |
| 702 | case "\\Downarrow": |
| 703 | case "\\updownarrow": |
| 704 | case "/": |
| 705 | case "\\backslash": |
| 706 | case "\\Updownarrow": |
| 707 | $passedArgs = array_merge( $passedArgs, [ "stretchy" => "true", "symmetric" => "true" ] ); |
| 708 | break; |
| 709 | } |
| 710 | |
| 711 | if ( in_array( $name, [ "\\bigl", "\\Bigl", "\\biggl", "\\Biggl" ] ) ) { |
| 712 | $passedArgs = array_merge( $passedArgs, [ Tag::CLASSTAG => TexClass::OPEN ] ); |
| 713 | } |
| 714 | |
| 715 | if ( in_array( $name, [ "\\bigr", "\\Bigr", "\\biggr", "\\Biggr" ] ) ) { |
| 716 | $passedArgs = array_merge( $passedArgs, [ Tag::CLASSTAG => TexClass::CLOSE ] ); |
| 717 | } |
| 718 | |
| 719 | $ret = $bm->checkAndParseDelimiter( $node->getArg(), $node, $passedArgs, $operatorContent, true ); |
| 720 | if ( !$ret->isEmpty() ) { |
| 721 | return $ret; |
| 722 | } |
| 723 | |
| 724 | $argPrep = $node->getArg(); |
| 725 | return new MMLmrow( TexClass::ORD, [], |
| 726 | new MMLmrow( $texClass, [], new MMLmo( "", $passedArgs, $argPrep ) ) |
| 727 | ); |
| 728 | } |
| 729 | |
| 730 | public static function machine( $node, $passedArgs, $operatorContent, $name, $type = null ): MMLbase { |
| 731 | // this could also be shifted to MhChem.php renderMML for ce |
| 732 | // For parsing chem (ce) or ??? (pu) |
| 733 | return new MMLmrow( "", [], $node->getArg()->toMMLtree() ); |
| 734 | } |
| 735 | |
| 736 | public static function setFont( $node, $passedArgs, $operatorContent, $name, $variant = null ): MMLbase { |
| 737 | return self::mathFont( $node, $passedArgs, $operatorContent, $name, $variant ); |
| 738 | } |
| 739 | |
| 740 | public static function sideset( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 741 | if ( !array_key_exists( "sideset", $operatorContent ) ) { |
| 742 | return MMLmerror::newFromText( "Error parsing sideset expression, no succeeding operator found" ); |
| 743 | } |
| 744 | |
| 745 | $op = $operatorContent["sideset"]; |
| 746 | $state = [ 'sideset' => true ]; |
| 747 | $in1 = $node->getArg1()->toMMLtree( [], $state ); |
| 748 | $in2 = $node->getArg2()->toMMLtree( [], $state ); |
| 749 | |
| 750 | if ( $op instanceof FQ || $op instanceof DQ || $op instanceof UQ ) { |
| 751 | $bm = new BaseMethods(); |
| 752 | if ( count( $op->getBase()->getArgs() ) == 1 ) { |
| 753 | $baseOperator = $op->getBase()->getArgs()[0]; |
| 754 | if ( is_string( $baseOperator ) ) { |
| 755 | $opParsed = $bm->checkAndParseOperator( $baseOperator, |
| 756 | null, [ "largeop" => "true", "symmetric" => "true" ], [], null ); |
| 757 | } else { |
| 758 | $opParsed = $baseOperator->toMMLTree(); |
| 759 | } |
| 760 | if ( $opParsed->isEmpty() ) { |
| 761 | $opParsed = $op->getBase()->toMMLtree(); |
| 762 | } |
| 763 | } else { |
| 764 | $opParsed = MMLmerror::newFromText( "Sideset operator parsing not implemented yet" ); |
| 765 | } |
| 766 | $down = $op instanceof UQ ? new MMLmrow( "", [] ) : $op->getDown()->toMMLtree(); |
| 767 | $up = $op instanceof DQ ? new MMLmrow( "", [] ) : $op->getUp()->toMMLtree(); |
| 768 | return new MMLmrow( |
| 769 | TexClass::OP, |
| 770 | [], |
| 771 | MMLmunderover::newSubtree( |
| 772 | new MMLmstyle( "", [ "displaystyle" => "true" ], |
| 773 | MMLmmultiscripts::newSubtree( $opParsed, $in2, new MMLarray(), $in1 ) ), |
| 774 | new MMLmrow( "", [], $down ), |
| 775 | new MMLmrow( "", [], $up ) |
| 776 | ) |
| 777 | ); |
| 778 | } |
| 779 | |
| 780 | if ( $op instanceof Literal ) { |
| 781 | $bm = new BaseMethods(); |
| 782 | $opParsed = $bm->checkAndParseOperator( $op->getArg(), null, [], [], null ); |
| 783 | if ( $opParsed->isEmpty() ) { |
| 784 | $opParsed = $op->toMMLtree(); |
| 785 | } |
| 786 | } else { |
| 787 | $opParsed = $op->toMMLtree(); |
| 788 | } |
| 789 | return new MMLmrow( TexClass::OP, [], |
| 790 | MMLmmultiscripts::newSubtree( $opParsed, $in2, new MMLarray(), $in1, new MMLarray(), |
| 791 | "", [ Tag::ALIGN => "left" ] |
| 792 | ) |
| 793 | ); |
| 794 | } |
| 795 | |
| 796 | public static function spacer( $node, $passedArgs, $operatorContent, $name, $withIn = null, $smth2 = null |
| 797 | ): MMLbase { |
| 798 | return new MMLmspace( "", [ "width" => MMLutil::round2em( $withIn ) ] ); |
| 799 | } |
| 800 | |
| 801 | public static function smash( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 802 | $mpArgs = []; |
| 803 | $inner = new MMLarray(); |
| 804 | if ( $node instanceof Fun2sq ) { |
| 805 | $arg1 = $node->getArg1(); |
| 806 | $arg1i = ""; |
| 807 | if ( $arg1->isCurly() ) { |
| 808 | $arg1i = $arg1->render(); |
| 809 | } |
| 810 | |
| 811 | if ( str_contains( $arg1i, "{b}" ) ) { |
| 812 | $mpArgs = [ "depth" => "0" ]; |
| 813 | } |
| 814 | if ( str_contains( $arg1i, "{t}" ) ) { |
| 815 | $mpArgs = [ "height" => "0" ]; |
| 816 | } |
| 817 | if ( str_contains( $arg1i, "{tb}" ) || str_contains( $arg1i, "{bt}" ) ) { |
| 818 | $mpArgs = [ "height" => "0", "depth" => "0" ]; |
| 819 | } |
| 820 | |
| 821 | $inner = $node->getArg2()->toMMLtree(); |
| 822 | } elseif ( $node instanceof Fun1 ) { |
| 823 | // Implicitly assume "tb" as default mode |
| 824 | $mpArgs = [ "height" => "0", "depth" => "0" ]; |
| 825 | $inner = $node->getArg()->toMMLtree(); |
| 826 | } |
| 827 | return new MMLmrow( TexClass::ORD, [], new MMLmpadded( "", $mpArgs, $inner ) ); |
| 828 | } |
| 829 | |
| 830 | public static function texAtom( $node, $passedArgs, $operatorContent, $name, $texClass = null ): MMLbase { |
| 831 | switch ( $name ) { |
| 832 | case '\mathbin': |
| 833 | // no break |
| 834 | case '\mathop': |
| 835 | // no break |
| 836 | case '\mathrel': |
| 837 | $inner = $node->getArg()->toMMLtree(); |
| 838 | return new MMLmrow( $texClass, [], $inner ); |
| 839 | default: |
| 840 | $inner = $node->getArg()->toMMLtree(); |
| 841 | return new MMLmrow( TexClass::ORD, [], new MMLmrow( $texClass, [], $inner ) ); |
| 842 | } |
| 843 | } |
| 844 | |
| 845 | public static function intent( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase { |
| 846 | if ( !$node instanceof Fun2 ) { |
| 847 | return new MMLarray(); |
| 848 | } |
| 849 | // if there is intent annotation add intent to root element |
| 850 | // match args in row of subargs, unless an element has explicit annotations |
| 851 | // nested annotations ? |
| 852 | $arg1 = $node->getArg1(); |
| 853 | $arg2 = $node->getArg2(); |
| 854 | if ( !$arg2->isCurly() ) { |
| 855 | return new MMLarray(); |
| 856 | } |
| 857 | // tbd refactor intent form and fiddle in mml or tree |
| 858 | $intentStr = MMLutil::squashLitsToUnitIntent( $arg2 ); |
| 859 | $intentContent = MMLParsingUtil::getIntentContent( $intentStr ); |
| 860 | $intentParams = MMLParsingUtil::getIntentParams( $intentContent ); |
| 861 | // Sometimes the intent has additioargs = {array[3]} nal args in the same string |
| 862 | $intentArg = MMLParsingUtil::getIntentArgs( $intentStr ); |
| 863 | if ( !$intentContent && !$intentParams && $intentArg !== null ) { |
| 864 | // explicit args annotation parsing in literal |
| 865 | // return $arg1->renderMML([],["intent-params-expl"=>$intentArg]); |
| 866 | // alternative just add the arg here |
| 867 | return $arg1->toMMLtree( [ "arg" => $intentArg ] ); |
| 868 | } |
| 869 | $intentContentAtr = [ "intent" => $intentContent ]; |
| 870 | if ( $intentArg !== null ) { |
| 871 | $intentContentAtr["arg"] = $intentArg; |
| 872 | } |
| 873 | // tbd refine intent params and operator content merging (does it overwrite ??) |
| 874 | $intentParamsState = $intentParams ? [ "intent-params" => $intentParams ] : $operatorContent; |
| 875 | // Here are some edge cases, they might go into renderMML in the related element |
| 876 | if ( str_contains( $intentContent ?? '', "matrix" ) || |
| 877 | ( $arg1->isCurly() && $arg1->getArgs()[0] instanceof Matrix ) ) { |
| 878 | $element = $arg1->getArgs()[0]; |
| 879 | $rendered = $element->toMMLtree( [], $intentParamsState ); |
| 880 | return MMLParsingUtil::forgeIntentToSpecificElement( $rendered, |
| 881 | $intentContentAtr, "mtable" ); |
| 882 | } elseif ( $arg1->isCurly() && count( $arg1->getArgs() ) >= 2 ) { |
| 883 | // Create a surrounding element which holds the intents |
| 884 | return new MMLmrow( "", $intentContentAtr, $arg1->toMMLtree( [], $intentParamsState ) ); |
| 885 | } elseif ( $arg1->isCurly() && count( $arg1->getArgs() ) >= 1 ) { |
| 886 | // Forge the intent attribute to the top-level element after MML rendering |
| 887 | $element = $arg1->getArgs()[0]; |
| 888 | $rendered = $element->toMMLtree( [], $intentParamsState ); |
| 889 | return MMLParsingUtil::forgeIntentToTopElement( $rendered, $intentContentAtr ); |
| 890 | } else { |
| 891 | // This is the default case |
| 892 | return $arg1->toMMLtree( $intentContentAtr, $intentParamsState ); |
| 893 | } |
| 894 | } |
| 895 | |
| 896 | public static function hBox( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase { |
| 897 | switch ( trim( $name ) ) { |
| 898 | case "\\mbox": |
| 899 | if ( isset( $operatorContent['foundOC'] ) ) { |
| 900 | $op = $operatorContent['foundOC']; |
| 901 | $macro = TexUtil::getInstance()->nullary_macro_in_mbox( $op ) ? |
| 902 | /* tested in \MediaWiki\Extension\Math\Tests\WikiTexVC\TexUtilTest::testUnicodeDefined */ |
| 903 | [ TexUtil::getInstance()->unicode_char( $op ) ] : |
| 904 | TexUtil::getInstance()->identifier( $op ); |
| 905 | $input = $macro[0] ?? $op; |
| 906 | return new MMLmrow( TexClass::ORD, [], new MMLmo( "", [], MMLutil::uc2xNotation( $input ) ) ); |
| 907 | } else { |
| 908 | return new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], "\mbox" ) ); |
| 909 | } |
| 910 | case "\\hbox": |
| 911 | $inner = $node->getArg() instanceof TexNode ? $node->getArg()->toMMLtree() : $node->getArg(); |
| 912 | return new MMLmrow( TexClass::ORD, [], |
| 913 | new MMLmstyle( "", [ "displaystyle" => "false", "scriptlevel" => "0" ], |
| 914 | new MMLmtext( "", [], $inner ) |
| 915 | ) |
| 916 | ); |
| 917 | case "\\text": |
| 918 | $inner = $node->getArg() instanceof TexNode ? $node->getArg()->toMMLtree() : $node->getArg(); |
| 919 | return new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], $inner ) ); |
| 920 | case "\\textbf": |
| 921 | // no break |
| 922 | case "\\textit": |
| 923 | // no break |
| 924 | case "\\textrm": |
| 925 | // no break |
| 926 | case "\\textsf": |
| 927 | // no break |
| 928 | case "\\texttt": |
| 929 | $state = [ "inHBox" => true, 'squashLiterals' => true ]; |
| 930 | $fontArgs = MMLParsingUtil::getFontArgs( $name, null, null ); |
| 931 | $inner = $node->getArg()->isCurly() ? $node->getArg()->toMMLtree( |
| 932 | $fontArgs, $state ) |
| 933 | : $node->getArg()->toMMLtree( $fontArgs ); |
| 934 | if ( $inner instanceof MMLbase ) { |
| 935 | $inner = $inner->getTextContent(); |
| 936 | } |
| 937 | return new MMLmtext( "", $fontArgs, $inner ?? '' ); |
| 938 | |
| 939 | } |
| 940 | |
| 941 | return MMLmerror::newFromText( "undefined hbox" ); |
| 942 | } |
| 943 | |
| 944 | public static function setStyle( $node, $passedArgs, $operatorContent, $name, |
| 945 | $smth = null, $smth1 = null, $smth2 = null |
| 946 | ): MMLmrow { |
| 947 | // Just discard setstyle since they are captured in TexArray now} |
| 948 | return new MMLmrow(); |
| 949 | } |
| 950 | |
| 951 | public static function not( $node, $passedArgs, $operatorContent, $name, $smth = null, |
| 952 | $smth1 = null, $smth2 = null |
| 953 | ): MMLbase { |
| 954 | // This is only tested for \not statement without follow-up parameters |
| 955 | if ( $node instanceof Literal ) { |
| 956 | return MMLParsingUtil::createNot(); |
| 957 | } |
| 958 | return MMLmerror::newFromText( "TBD implement not" ); |
| 959 | } |
| 960 | |
| 961 | public static function vbox( $node, $passedArgs, $operatorContent, $name, $smth = null ): MMLbase { |
| 962 | // This is only example functionality for vbox("ab"). |
| 963 | // TBD: it should be discussed if vbox is supported since it |
| 964 | // does not seem to be supported by mathjax |
| 965 | if ( is_string( $node->getArg() ) ) { |
| 966 | $arr1 = str_split( $node->getArg() ); |
| 967 | $inner = []; |
| 968 | foreach ( $arr1 as $char ) { |
| 969 | $inner[] = new MMLmrow( TexClass::ORD, [], new MMLmtext( "", [], $char ) ); |
| 970 | } |
| 971 | return MMLmover::newSubtree( $inner[0], $inner[1] ); |
| 972 | } |
| 973 | return MMLmerror::newFromText( "no implemented vbox" ); |
| 974 | } |
| 975 | |
| 976 | public static function sqrt( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 977 | // There is an additional argument for the root |
| 978 | if ( $node instanceof Fun2sq ) { |
| 979 | // In case of an empty curly add an mrow |
| 980 | $arg2Rendered = $node->getArg2()->toMMLtree( $passedArgs ); |
| 981 | if ( $arg2Rendered->isEmpty() ) { |
| 982 | $arg2Rendered = new MMLmrow( TexClass::ORD, [] ); |
| 983 | } |
| 984 | return new MMLmrow( TexClass::ORD, [], |
| 985 | MMLmroot::newSubtree( |
| 986 | $arg2Rendered, |
| 987 | new MMLmrow( TexClass::ORD, [], |
| 988 | $node->getArg1()->toMMLtree( $passedArgs ) |
| 989 | ) |
| 990 | ) |
| 991 | ); |
| 992 | } |
| 993 | // Currently this is own implementation from Fun1.php |
| 994 | return new MMLmrow( TexClass::ORD, [], // assuming that this is always encapsulated in mrow |
| 995 | new MMLmsqrt( "", [], |
| 996 | $node->getArg()->toMMLtree( $passedArgs ) |
| 997 | ) |
| 998 | ); |
| 999 | } |
| 1000 | |
| 1001 | public static function tilde( $node, $passedArgs, $operatorContent, $name ): MMLbase { |
| 1002 | return new MMLmspace( "", [ "width" => "0.5em" ] ); |
| 1003 | } |
| 1004 | |
| 1005 | public static function xArrow( $node, $passedArgs, $operatorContent, $name, $chr = null, $l = null, |
| 1006 | $r = null ): MMLbase { |
| 1007 | $defWidth = "+" . MMLutil::round2em( ( $l + $r ) / 18 ); |
| 1008 | $defLspace = MMLutil::round2em( $l / 18 ); |
| 1009 | |
| 1010 | $char = IntlChar::chr( $chr ); |
| 1011 | |
| 1012 | // Core has no relative width; Chrome reads "+0.833em" as 0.833em. |
| 1013 | $mpaddedArgs = [ "height" => "-.2em", "lspace" => $defLspace, "voffset" => "-.2em", |
| 1014 | "data-mwe-width" => $defWidth ]; |
| 1015 | $mspace = new MMLmspace( "", [ "depth" => ".25em" ] ); |
| 1016 | if ( $node instanceof Fun2sq ) { |
| 1017 | return new MMLmrow( TexClass::ORD, [], MMLmunderover::newSubtree( |
| 1018 | new MMLmstyle( "", [ "scriptlevel" => "0" ], |
| 1019 | new MMLmo( Texclass::REL, [], $char ) |
| 1020 | ), |
| 1021 | new MMLmpadded( "", $mpaddedArgs, |
| 1022 | new MMLmrow( TexClass::ORD, [], |
| 1023 | $node->getArg1()->toMMLtree() |
| 1024 | ), |
| 1025 | $mspace |
| 1026 | ), |
| 1027 | new MMLmpadded( "", $mpaddedArgs, |
| 1028 | $node->getArg2()->toMMLtree() |
| 1029 | ) |
| 1030 | ) ); |
| 1031 | |
| 1032 | } |
| 1033 | return MMLmover::newSubtree( |
| 1034 | new MMLmstyle( "", [ "scriptlevel" => "0" ], new MMLmo( Texclass::REL, [], $char ) ), |
| 1035 | new MMLmpadded( "", $mpaddedArgs, $node->getArg()->toMMLtree(), $mspace ) |
| 1036 | ); |
| 1037 | } |
| 1038 | |
| 1039 | public static function getApplyFct( array $operatorContent ): MMLbase { |
| 1040 | $applyFct = new MMLarray(); |
| 1041 | if ( array_key_exists( "foundNamedFct", $operatorContent ) ) { |
| 1042 | $hasNamedFct = $operatorContent['foundNamedFct'][0]; |
| 1043 | $hasValidParameters = $operatorContent["foundNamedFct"][1]; |
| 1044 | if ( $hasNamedFct && $hasValidParameters ) { |
| 1045 | $applyFct = MMLParsingUtil::renderApplyFunction(); |
| 1046 | } |
| 1047 | } |
| 1048 | return $applyFct; |
| 1049 | } |
| 1050 | } |