Create Math.RendererFactory service

Change-Id: I474d746bae81ddf4322814c45141c981ecdb077b
This commit is contained in:
Petr Pchelko 2021-07-30 12:17:20 -07:00
parent e9bc5d3a42
commit e6507e95d7
9 changed files with 213 additions and 75 deletions

View file

@ -2,6 +2,7 @@
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\Math\InputCheck\InputCheckFactory;
use MediaWiki\Extension\Math\Render\RendererFactory;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
@ -17,4 +18,14 @@ return [
LoggerFactory::getInstance( 'Math' )
);
},
'Math.RendererFactory' => static function ( MediaWikiServices $services ): RendererFactory {
return new RendererFactory(
new ServiceOptions(
RendererFactory::CONSTRUCTOR_OPTIONS,
$services->getMainConfig()
),
$services->getUserOptionsLookup(),
LoggerFactory::getInstance( 'Math' )
);
},
];

View file

@ -48,6 +48,7 @@
"ParserHooksHandler": {
"class": "MediaWiki\\Extension\\Math\\HookHandlers\\ParserHooksHandler",
"services": [
"Math.RendererFactory",
"UserOptionsLookup"
]
}
@ -293,8 +294,18 @@
"remoteExtPath": "Math/modules"
},
"SpecialPages": {
"MathShowImage": "MediaWiki\\Extension\\Math\\SpecialMathShowImage",
"MathStatus": "MediaWiki\\Extension\\Math\\SpecialMathStatus",
"MathShowImage": {
"class": "MediaWiki\\Extension\\Math\\SpecialMathShowImage",
"services": [
"Math.RendererFactory"
]
},
"MathStatus": {
"class": "MediaWiki\\Extension\\Math\\SpecialMathStatus",
"services": [
"Math.RendererFactory"
]
},
"MathWikibase": "MediaWiki\\Extension\\Math\\SpecialMathWikibase"
},
"TrackingCategories": [

View file

@ -8,6 +8,7 @@ use MediaWiki\Extension\Math\Hooks;
use MediaWiki\Extension\Math\MathMathML;
use MediaWiki\Extension\Math\MathMathMLCli;
use MediaWiki\Extension\Math\MathRenderer;
use MediaWiki\Extension\Math\Render\RendererFactory;
use MediaWiki\Hook\ParserAfterTidyHook;
use MediaWiki\Hook\ParserFirstCallInitHook;
use MediaWiki\Logger\LoggerFactory;
@ -26,15 +27,24 @@ class ParserHooksHandler implements
/** @var int */
private $mathTagCounter = 1;
/** @var array */
private $mathTags = [];
/** @var array[] renders delayed to be done as a batch [ MathRenderer, Parser ] */
private $mathLazyRenderBatch = [];
/** @var RendererFactory */
private $rendererFactory;
/** @var UserOptionsLookup */
private $userOptionsLookup;
/**
* @param RendererFactory $rendererFactory
* @param UserOptionsLookup $userOptionsLookup
*/
public function __construct(
RendererFactory $rendererFactory,
UserOptionsLookup $userOptionsLookup
) {
$this->rendererFactory = $rendererFactory;
$this->userOptionsLookup = $userOptionsLookup;
}
@ -69,7 +79,7 @@ class ParserHooksHandler implements
// Indicate that this page uses math.
// This affects the page caching behavior.
$parser->getOptions()->optionUsed( 'math' );
$renderer = MathRenderer::getRenderer( $content, $attributes, $mode );
$renderer = $this->rendererFactory->getRenderer( $content, $attributes, $mode );
$parser->getOutput()->addModuleStyles( [ 'ext.math.styles' ] );
if ( $mode == 'mathml' ) {
@ -77,7 +87,7 @@ class ParserHooksHandler implements
$marker = Parser::MARKER_PREFIX .
'-postMath-' . sprintf( '%08X', $this->mathTagCounter++ ) .
Parser::MARKER_SUFFIX;
$this->mathTags[$marker] = [ $renderer, $parser ];
$this->mathLazyRenderBatch[$marker] = [ $renderer, $parser ];
return $marker;
}
return [ $this->mathPostTagHook( $renderer, $parser ), 'markerType' => 'nowiki' ];
@ -144,23 +154,23 @@ class ParserHooksHandler implements
public function onParserAfterTidy( $parser, &$text ) {
global $wgMathoidCli;
if ( $wgMathoidCli ) {
MathMathMLCli::batchEvaluate( $this->mathTags );
MathMathMLCli::batchEvaluate( $this->mathLazyRenderBatch );
} else {
MathMathML::batchEvaluate( $this->mathTags );
MathMathML::batchEvaluate( $this->mathLazyRenderBatch );
}
foreach ( $this->mathTags as $key => $tag ) {
$value = $this->mathPostTagHook( ...$tag );
foreach ( $this->mathLazyRenderBatch as $key => [ $renderer, $renderParser ] ) {
$value = $this->mathPostTagHook( $renderer, $renderParser );
// Workaround for https://phabricator.wikimedia.org/T103269
$text = preg_replace(
'/(<mw:editsection[^>]*>.*?)' . preg_quote( $key ) . '(.*?)<\/mw:editsection>/',
'\1 $' . htmlspecialchars( $tag[0]->getTex() ) . '\2</mw:editsection>',
'\1 $' . htmlspecialchars( $renderer->getTex() ) . '\2</mw:editsection>',
$text
);
$count = 0;
$text = str_replace( $key, $value, $text, $count );
if ( $count ) {
// This hook might be called multiple times. However once the tag is rendered the job is done.
unset( $this->mathTags[ $key ] );
unset( $this->mathLazyRenderBatch[ $key ] );
}
}
}

View file

@ -14,6 +14,7 @@ namespace MediaWiki\Extension\Math;
use DeferredUpdates;
use MediaWiki\Extension\Math\InputCheck\RestbaseChecker;
use MediaWiki\Logger\LoggerFactory;
use MediaWiki\MediaWikiServices;
use MWException;
use Parser;
use Psr\Log\LoggerInterface;
@ -129,7 +130,9 @@ abstract class MathRenderer {
* @return string HTML for math tag
*/
public static function renderMath( $tex, $params = [], $mode = 'png' ) {
$renderer = self::getRenderer( $tex, $params, $mode );
$renderer = MediaWikiServices::getInstance()
->get( 'Math.RendererFactory' )
->getRenderer( $tex, $params, $mode );
if ( $renderer->render() ) {
return $renderer->getHtmlOutput();
} else {
@ -152,58 +155,16 @@ abstract class MathRenderer {
/**
* Static factory method for getting a renderer based on mode
*
* @deprecated since 3.0.0. Use Math.RendererFactory service instead.
* @param string $tex LaTeX markup
* @param array $params HTML attributes
* @param string $mode indicating rendering mode
* @return self appropriate renderer for mode
*/
public static function getRenderer( $tex, $params = [], $mode = 'png' ) {
global $wgDefaultUserOptions, $wgMathEnableExperimentalInputFormats, $wgMathoidCli;
if ( isset( $params['forcemathmode'] ) ) {
$mode = $params['forcemathmode'];
}
if ( !in_array( $mode, self::getValidModes() ) ) {
$mode = $wgDefaultUserOptions['math'];
}
if ( $wgMathEnableExperimentalInputFormats === true && $mode == 'mathml' &&
isset( $params['type'] ) ) {
// Support of MathML input (experimental)
// Currently support for mode 'mathml' only
if ( !in_array( $params['type'], [ 'pmml', 'ascii' ] ) ) {
unset( $params['type'] );
}
}
if ( isset( $params['chem'] ) ) {
$mode = 'mathml';
$params['type'] = 'chem';
}
switch ( $mode ) {
case 'source':
$renderer = new MathSource( $tex, $params );
break;
case 'png':
$renderer = new MathPng( $tex, $params );
break;
case 'latexml':
$renderer = new MathLaTeXML( $tex, $params );
break;
case 'mathml':
default:
if ( $wgMathoidCli ) {
$renderer = new MathMathMLCli( $tex, $params );
} else {
$renderer = new MathMathML( $tex, $params );
}
}
LoggerFactory::getInstance( 'Math' )->debug(
'Start rendering "{tex}" in mode {mode}',
[
'tex' => $tex,
'mode' => $mode
]
);
return $renderer;
return MediaWikiServices::getInstance()
->get( 'Math.RendererFactory' )
->getRenderer( $tex, $params, $mode );
}
/**
@ -717,6 +678,11 @@ abstract class MathRenderer {
return $names[ $this->getMode() ];
}
/**
* @deprecated since 3.0.0. Use 'Math.RendererFactory' service instead.
*
* @return array
*/
public static function getValidModes() {
global $wgMathValidModes;
return array_map( "MediaWiki\\Extension\\Math\\Hooks::mathModeToString", $wgMathValidModes );

View file

@ -0,0 +1,122 @@
<?php
namespace MediaWiki\Extension\Math\Render;
use MathMathML;
use MediaWiki\Config\ServiceOptions;
use MediaWiki\Extension\Math\Hooks;
use MediaWiki\Extension\Math\MathLaTeXML;
use MediaWiki\Extension\Math\MathMathMLCli;
use MediaWiki\Extension\Math\MathPng;
use MediaWiki\Extension\Math\MathRenderer;
use MediaWiki\Extension\Math\MathSource;
use MediaWiki\User\UserOptionsLookup;
use Psr\Log\LoggerInterface;
class RendererFactory {
/** @var string[] */
public const CONSTRUCTOR_OPTIONS = [
'MathoidCli',
'MathEnableExperimentalInputFormats',
'MathValidModes',
];
/** @var ServiceOptions */
private $options;
/** @var UserOptionsLookup */
private $userOptionsLookup;
/** @var LoggerInterface */
private $logger;
/**
* @param ServiceOptions $serviceOptions
* @param UserOptionsLookup $userOptionsLookup
* @param LoggerInterface $logger
*/
public function __construct(
ServiceOptions $serviceOptions,
UserOptionsLookup $userOptionsLookup,
LoggerInterface $logger
) {
$serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
$this->options = $serviceOptions;
$this->userOptionsLookup = $userOptionsLookup;
$this->logger = $logger;
}
/**
* Get valid math rendering modes
*
* @return string[]
*/
public function getValidModes(): array {
return array_map(
[ Hooks::class, 'mathModeToString' ],
$this->options->get( 'MathValidModes' )
);
}
/**
* Factory method for getting a renderer based on mode
*
* @param string $tex LaTeX markup
* @param array $params HTML attributes
* @param string $mode indicating rendering mode, one of ::getValidModes
* @return MathRenderer appropriate renderer for mode
*/
public function getRenderer(
string $tex,
array $params = [],
string $mode = 'png'
): MathRenderer {
if ( isset( $params['forcemathmode'] ) ) {
$mode = $params['forcemathmode'];
}
if ( !in_array( $mode, $this->getValidModes() ) ) {
$mode = $this->userOptionsLookup->getDefaultOption( 'math' );
}
if ( $this->options->get( 'MathEnableExperimentalInputFormats' ) === true &&
$mode == 'mathml' &&
isset( $params['type'] )
) {
// Support of MathML input (experimental)
// Currently support for mode 'mathml' only
if ( !in_array( $params['type'], [ 'pmml', 'ascii' ] ) ) {
unset( $params['type'] );
}
}
if ( isset( $params['chem'] ) ) {
$mode = 'mathml';
$params['type'] = 'chem';
}
switch ( $mode ) {
case 'source':
$renderer = new MathSource( $tex, $params );
break;
case 'png':
$renderer = new MathPng( $tex, $params );
break;
case 'latexml':
$renderer = new MathLaTeXML( $tex, $params );
break;
case 'mathml':
default:
if ( $this->options->get( 'MathoidCli' ) ) {
$renderer = new MathMathMLCli( $tex, $params );
} else {
$renderer = new MathMathML( $tex, $params );
}
}
$this->logger->debug(
'Start rendering "{tex}" in mode {mode}',
[
'tex' => $tex,
'mode' => $mode
]
);
return $renderer;
}
}

View file

@ -2,6 +2,7 @@
namespace MediaWiki\Extension\Math;
use MediaWiki\Extension\Math\Render\RendererFactory;
use SpecialPage;
/**
@ -17,12 +18,21 @@ class SpecialMathShowImage extends SpecialPage {
/** @var string */
private $mode = 'mathml';
public function __construct() {
/** @var RendererFactory */
private $rendererFactory;
/**
* @param RendererFactory $rendererFactory
*/
public function __construct(
RendererFactory $rendererFactory
) {
parent::__construct(
'MathShowImage',
'', // Don't restrict
false // Don't show on Special:SpecialPages - it's not useful interactively
);
$this->rendererFactory = $rendererFactory;
}
/**
@ -72,9 +82,9 @@ class SpecialMathShowImage extends SpecialPage {
} else {
if ( $tex === '' && $asciimath === '' ) {
if ( $wgMathoidCli && $this->mode === 'png' ) {
$this->renderer = MathRenderer::getRenderer( '', [], 'mathml' );
$this->renderer = $this->rendererFactory->getRenderer( '', [], 'mathml' );
} else {
$this->renderer = MathRenderer::getRenderer( '', [], $this->mode );
$this->renderer = $this->rendererFactory->getRenderer( '', [], $this->mode );
}
$this->renderer->setMd5( $hash );
$this->noRender = $request->getBool( 'noRender', false );
@ -87,7 +97,7 @@ class SpecialMathShowImage extends SpecialPage {
// and render the conventional way
$mmlRenderer = MathMathML::newFromMd5( $hash );
$mmlRenderer->readFromDatabase();
$this->renderer = MathRenderer::getRenderer(
$this->renderer = $this->rendererFactory->getRenderer(
$mmlRenderer->getUserInputTex(), [], 'png'
);
$this->renderer->setMathStyle( $mmlRenderer->getMathStyle() );
@ -95,10 +105,10 @@ class SpecialMathShowImage extends SpecialPage {
$success = $this->renderer->render();
}
} elseif ( $asciimath === '' ) {
$this->renderer = MathRenderer::getRenderer( $tex, [], $this->mode );
$this->renderer = $this->rendererFactory->getRenderer( $tex, [], $this->mode );
$success = $this->renderer->render();
} else {
$this->renderer = MathRenderer::getRenderer(
$this->renderer = $this->rendererFactory->getRenderer(
$asciimath, [ 'type' => 'ascii' ], $this->mode
);
$success = $this->renderer->render();

View file

@ -3,6 +3,7 @@
namespace MediaWiki\Extension\Math;
use ExtensionRegistry;
use MediaWiki\Extension\Math\Render\RendererFactory;
use MediaWiki\Logger\LoggerFactory;
use MWException;
use PermissionsError;
@ -21,9 +22,15 @@ class SpecialMathStatus extends SpecialPage {
/** @var LoggerInterface */
private $logger;
public function __construct( $name = 'MathStatus' ) {
parent::__construct( $name, 'purge' );
/** @var RendererFactory */
private $rendererFactory;
public function __construct(
RendererFactory $rendererFactory
) {
parent::__construct( 'MathStatus', 'purge' );
$this->rendererFactory = $rendererFactory;
$this->logger = LoggerFactory::getInstance( 'Math' );
}
@ -68,7 +75,7 @@ class SpecialMathStatus extends SpecialPage {
}
public function testSpecialCaseText() {
$renderer = MathRenderer::getRenderer( 'x^2+\text{a sample Text}', [], 'mathml' );
$renderer = $this->rendererFactory->getRenderer( 'x^2+\text{a sample Text}', [], 'mathml' );
$expected = 'a sample Text</mtext>';
$this->assertTrue( $renderer->render(), 'Rendering the input "x^2+\text{a sample Text}"' );
$this->assertContains(
@ -83,7 +90,7 @@ class SpecialMathStatus extends SpecialPage {
public function testMathMLIntegration() {
$svgRef = file_get_contents( __DIR__ . '/../images/reference.svg' );
$svgRefNoSpeech = file_get_contents( __DIR__ . '/../images/reference-nospeech.svg' );
$renderer = MathRenderer::getRenderer( "a+b", [], 'mathml' );
$renderer = $this->rendererFactory->getRenderer( "a+b", [], 'mathml' );
$this->assertTrue( $renderer->render(), "Rendering of a+b in plain MathML mode" );
$real = str_replace( "\n", '', $renderer->getHtmlOutput() );
$expected = '<mo>+</mo>';
@ -117,7 +124,7 @@ class SpecialMathStatus extends SpecialPage {
* i.e. if the span element is generated right.
*/
public function testLaTeXMLIntegration() {
$renderer = MathRenderer::getRenderer( "a+b", [], 'latexml' );
$renderer = $this->rendererFactory->getRenderer( "a+b", [], 'latexml' );
$this->assertTrue( $renderer->render(), "Rendering of a+b in LaTeXML mode" );
// phpcs:ignore Generic.Files.LineLength.TooLong
$expected = '<math xmlns="http://www.w3.org/1998/Math/MathML" id="p1.m1" class="ltx_Math" alttext="{\displaystyle a+b}" ><semantics><mrow id="p1.m1.4" xref="p1.m1.4.cmml"><mi id="p1.m1.1" xref="p1.m1.1.cmml">a</mi><mo id="p1.m1.2" xref="p1.m1.2.cmml">+</mo><mi id="p1.m1.3" xref="p1.m1.3.cmml">b</mi></mrow><annotation-xml encoding="MathML-Content"><apply id="p1.m1.4.cmml" xref="p1.m1.4"><plus id="p1.m1.2.cmml" xref="p1.m1.2"/><ci id="p1.m1.1.cmml" xref="p1.m1.1">a</ci><ci id="p1.m1.3.cmml" xref="p1.m1.3">b</ci></apply></annotation-xml><annotation encoding="application/x-tex">{\displaystyle a+b}</annotation></semantics></math>';

View file

@ -1,7 +1,5 @@
<?php
use MediaWiki\Extension\Math\MathRenderer;
/**
* Test the Id feature
*
@ -11,14 +9,16 @@ use MediaWiki\Extension\Math\MathRenderer;
*
* @license GPL-2.0-or-later
*/
class MathIdTest extends MediaWikiTestCase {
class MathIdTest extends MediaWikiIntegrationTestCase {
/**
* Checks if the id specified as attribute is set in the renderer object
*/
public function testBasics() {
define( 'RANDOM_ID', 'a_random_id' );
$renderer = MathRenderer::getRenderer( "a+b", [ 'id' => RANDOM_ID ] );
$renderer = $this->getServiceContainer()
->get( 'Math.RendererFactory' )
->getRenderer( "a+b", [ 'id' => RANDOM_ID ] );
$this->assertEquals( RANDOM_ID, $renderer->getID() );
}

View file

@ -62,9 +62,10 @@ class WfTest extends Maintenance {
}
$i = 0;
$rend = [];
$rendererFactory = MediaWikiServices::getInstance()->get( 'Math.RendererFactory' );
foreach ( array_slice( $allEquations, $offset, $length, true ) as $input ) {
$output = MathRenderer::renderMath( $input[1], $input[2], 'mathml' );
$rend[] = [ MathRenderer::getRenderer( $input[1], $input[2], 'mathml' ), $input ];
$rend[] = [ $rendererFactory->getRenderer( $input[1], $input[2], 'mathml' ), $input ];
$output = preg_replace( '#src="(.*?)/(([a-f]|\d)*).png"#', 'src="\2.png"', $output );
$parserTests[] = [ (string)$input[1], $output ];
$i++;